Squashed 'external/libwebp/libwebp/' content from commit dd7364c3c
git-subtree-dir: external/libwebp/libwebp git-subtree-split: dd7364c3cefe0f5c0b3c18c3b1887d353f90fc1f
This commit is contained in:
96
examples/Android.mk
Normal file
96
examples/Android.mk
Normal file
@@ -0,0 +1,96 @@
|
||||
# Ignore this file during non-NDK builds.
|
||||
ifdef NDK_ROOT
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
################################################################################
|
||||
# libexample_util
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
example_util.c \
|
||||
|
||||
LOCAL_CFLAGS := $(WEBP_CFLAGS)
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../src
|
||||
|
||||
LOCAL_MODULE := example_util
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
################################################################################
|
||||
# cwebp
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
# Note: to enable jpeg/png encoding the sources from AOSP can be used with
|
||||
# minor modification to their Android.mk files.
|
||||
LOCAL_SRC_FILES := \
|
||||
cwebp.c \
|
||||
|
||||
LOCAL_CFLAGS := $(WEBP_CFLAGS)
|
||||
LOCAL_STATIC_LIBRARIES := example_util imageio_util imagedec webpdemux webp
|
||||
|
||||
LOCAL_MODULE := cwebp
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
################################################################################
|
||||
# dwebp
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
dwebp.c \
|
||||
|
||||
LOCAL_CFLAGS := $(WEBP_CFLAGS)
|
||||
LOCAL_STATIC_LIBRARIES := example_util imagedec imageenc webpdemux webp
|
||||
LOCAL_MODULE := dwebp
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
################################################################################
|
||||
# webpmux
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
webpmux.c \
|
||||
|
||||
LOCAL_CFLAGS := $(WEBP_CFLAGS)
|
||||
LOCAL_STATIC_LIBRARIES := example_util imageio_util webpmux webp
|
||||
|
||||
LOCAL_MODULE := webpmux_example
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
################################################################################
|
||||
# img2webp
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
img2webp.c \
|
||||
|
||||
LOCAL_CFLAGS := $(WEBP_CFLAGS)
|
||||
LOCAL_STATIC_LIBRARIES := example_util imageio_util imagedec webpmux webpdemux \
|
||||
webp
|
||||
|
||||
LOCAL_MODULE := img2webp_example
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
################################################################################
|
||||
# webpinfo
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
webpinfo.c \
|
||||
|
||||
LOCAL_CFLAGS := $(WEBP_CFLAGS)
|
||||
LOCAL_STATIC_LIBRARIES := example_util imageio_util webp
|
||||
|
||||
LOCAL_MODULE := webpinfo_example
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
endif # NDK_ROOT
|
119
examples/Makefile.am
Normal file
119
examples/Makefile.am
Normal file
@@ -0,0 +1,119 @@
|
||||
AM_CPPFLAGS += -I$(top_builddir)/src -I$(top_srcdir)/src
|
||||
|
||||
bin_PROGRAMS =
|
||||
if BUILD_DEMUX
|
||||
bin_PROGRAMS += dwebp cwebp
|
||||
endif
|
||||
if BUILD_ANIMDIFF
|
||||
noinst_PROGRAMS = anim_diff anim_dump
|
||||
endif
|
||||
if BUILD_GIF2WEBP
|
||||
bin_PROGRAMS += gif2webp
|
||||
endif
|
||||
if BUILD_IMG2WEBP
|
||||
bin_PROGRAMS += img2webp
|
||||
endif
|
||||
if BUILD_MUX
|
||||
bin_PROGRAMS += webpmux
|
||||
endif
|
||||
if BUILD_VWEBP
|
||||
bin_PROGRAMS += vwebp
|
||||
endif
|
||||
if BUILD_WEBPINFO
|
||||
bin_PROGRAMS += webpinfo
|
||||
endif
|
||||
|
||||
noinst_LTLIBRARIES = libexample_util.la
|
||||
|
||||
libexample_util_la_SOURCES = example_util.c example_util.h
|
||||
libexample_util_la_LIBADD = ../src/libwebp.la
|
||||
|
||||
anim_diff_SOURCES = anim_diff.c anim_util.c anim_util.h gifdec.c gifdec.h
|
||||
anim_diff_CPPFLAGS = $(AM_CPPFLAGS) $(GIF_INCLUDES)
|
||||
anim_diff_LDADD =
|
||||
anim_diff_LDADD += ../src/demux/libwebpdemux.la
|
||||
anim_diff_LDADD += libexample_util.la
|
||||
anim_diff_LDADD += ../imageio/libimageio_util.la
|
||||
anim_diff_LDADD += $(GIF_LIBS) -lm
|
||||
|
||||
anim_dump_SOURCES = anim_dump.c anim_util.c anim_util.h gifdec.c gifdec.h
|
||||
anim_dump_CPPFLAGS = $(AM_CPPFLAGS) $(PNG_INCLUDES)
|
||||
anim_dump_CPPFLAGS += $(GIF_INCLUDES)
|
||||
anim_dump_LDADD =
|
||||
anim_dump_LDADD += ../src/demux/libwebpdemux.la
|
||||
anim_dump_LDADD += libexample_util.la
|
||||
anim_dump_LDADD += ../imageio/libimageio_util.la
|
||||
anim_dump_LDADD += ../imageio/libimageenc.la
|
||||
anim_dump_LDADD += $(PNG_LIBS) $(GIF_LIBS) $(TIFF_LIBS) -lm
|
||||
|
||||
cwebp_SOURCES = cwebp.c stopwatch.h
|
||||
cwebp_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)
|
||||
cwebp_LDADD =
|
||||
cwebp_LDADD += libexample_util.la
|
||||
cwebp_LDADD += ../imageio/libimageio_util.la
|
||||
cwebp_LDADD += ../imageio/libimagedec.la
|
||||
cwebp_LDADD += ../src/libwebp.la
|
||||
cwebp_LDADD += $(JPEG_LIBS) $(PNG_LIBS) $(TIFF_LIBS)
|
||||
|
||||
dwebp_SOURCES = dwebp.c stopwatch.h
|
||||
dwebp_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
dwebp_CPPFLAGS += $(JPEG_INCLUDES) $(PNG_INCLUDES)
|
||||
dwebp_LDADD =
|
||||
dwebp_LDADD += libexample_util.la
|
||||
dwebp_LDADD += ../imageio/libimagedec.la
|
||||
dwebp_LDADD += ../imageio/libimageenc.la
|
||||
dwebp_LDADD += ../imageio/libimageio_util.la
|
||||
dwebp_LDADD += ../src/libwebp.la
|
||||
dwebp_LDADD +=$(PNG_LIBS) $(JPEG_LIBS)
|
||||
|
||||
gif2webp_SOURCES = gif2webp.c gifdec.c gifdec.h
|
||||
gif2webp_CPPFLAGS = $(AM_CPPFLAGS) $(GIF_INCLUDES)
|
||||
gif2webp_LDADD =
|
||||
gif2webp_LDADD += libexample_util.la
|
||||
gif2webp_LDADD += ../imageio/libimageio_util.la
|
||||
gif2webp_LDADD += ../src/mux/libwebpmux.la
|
||||
gif2webp_LDADD += ../src/libwebp.la
|
||||
gif2webp_LDADD += $(GIF_LIBS)
|
||||
|
||||
vwebp_SOURCES = vwebp.c
|
||||
vwebp_CPPFLAGS = $(AM_CPPFLAGS) $(GL_INCLUDES)
|
||||
vwebp_LDADD =
|
||||
vwebp_LDADD += libexample_util.la
|
||||
vwebp_LDADD += ../imageio/libimageio_util.la
|
||||
vwebp_LDADD += ../src/demux/libwebpdemux.la
|
||||
vwebp_LDADD += $(GL_LIBS)
|
||||
|
||||
webpmux_SOURCES = webpmux.c
|
||||
webpmux_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
webpmux_LDADD =
|
||||
webpmux_LDADD += libexample_util.la
|
||||
webpmux_LDADD += ../imageio/libimageio_util.la
|
||||
webpmux_LDADD += ../src/mux/libwebpmux.la
|
||||
webpmux_LDADD += ../src/libwebp.la
|
||||
|
||||
img2webp_SOURCES = img2webp.c
|
||||
img2webp_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)
|
||||
img2webp_LDADD =
|
||||
img2webp_LDADD += libexample_util.la
|
||||
img2webp_LDADD += ../imageio/libimageio_util.la
|
||||
img2webp_LDADD += ../imageio/libimagedec.la
|
||||
img2webp_LDADD += ../src/mux/libwebpmux.la
|
||||
img2webp_LDADD += ../src/libwebp.la
|
||||
img2webp_LDADD += $(PNG_LIBS) $(JPEG_LIBS) $(TIFF_LIBS)
|
||||
|
||||
webpinfo_SOURCES = webpinfo.c
|
||||
webpinfo_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
webpinfo_LDADD =
|
||||
webpinfo_LDADD += libexample_util.la
|
||||
webpinfo_LDADD += ../imageio/libimageio_util.la
|
||||
webpinfo_LDADD += ../src/libwebp.la
|
||||
|
||||
if BUILD_LIBWEBPDECODER
|
||||
anim_diff_LDADD += ../src/libwebpdecoder.la
|
||||
anim_dump_LDADD += ../src/libwebpdecoder.la
|
||||
vwebp_LDADD += ../src/libwebpdecoder.la
|
||||
else
|
||||
anim_diff_LDADD += ../src/libwebp.la
|
||||
anim_dump_LDADD += ../src/libwebp.la
|
||||
vwebp_LDADD += ../src/libwebp.la
|
||||
endif
|
317
examples/anim_diff.c
Normal file
317
examples/anim_diff.c
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright 2015 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Checks if given pair of animated GIF/WebP images are identical:
|
||||
// That is: their reconstructed canvases match pixel-by-pixel and their other
|
||||
// animation properties (loop count etc) also match.
|
||||
//
|
||||
// example: anim_diff foo.gif bar.webp
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // for 'strtod'.
|
||||
#include <string.h> // for 'strcmp'.
|
||||
|
||||
#include "./anim_util.h"
|
||||
#include "./example_util.h"
|
||||
#include "./unicode.h"
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
// Returns true if 'a + b' will overflow.
|
||||
static int AdditionWillOverflow(int a, int b) {
|
||||
return (b > 0) && (a > INT_MAX - b);
|
||||
}
|
||||
|
||||
static int FramesAreEqual(const uint8_t* const rgba1,
|
||||
const uint8_t* const rgba2, int width, int height) {
|
||||
const int stride = width * 4; // Always true for 'DecodedFrame.rgba'.
|
||||
return !memcmp(rgba1, rgba2, stride * height);
|
||||
}
|
||||
|
||||
static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst,
|
||||
int max_allowed_diff) {
|
||||
const int src_a = (src >> 24) & 0xff;
|
||||
const int src_r = (src >> 16) & 0xff;
|
||||
const int src_g = (src >> 8) & 0xff;
|
||||
const int src_b = (src >> 0) & 0xff;
|
||||
const int dst_a = (dst >> 24) & 0xff;
|
||||
const int dst_r = (dst >> 16) & 0xff;
|
||||
const int dst_g = (dst >> 8) & 0xff;
|
||||
const int dst_b = (dst >> 0) & 0xff;
|
||||
|
||||
return (abs(src_r * src_a - dst_r * dst_a) <= (max_allowed_diff * 255)) &&
|
||||
(abs(src_g * src_a - dst_g * dst_a) <= (max_allowed_diff * 255)) &&
|
||||
(abs(src_b * src_a - dst_b * dst_a) <= (max_allowed_diff * 255)) &&
|
||||
(abs(src_a - dst_a) <= max_allowed_diff);
|
||||
}
|
||||
|
||||
static int FramesAreSimilar(const uint8_t* const rgba1,
|
||||
const uint8_t* const rgba2,
|
||||
int width, int height, int max_allowed_diff) {
|
||||
int i, j;
|
||||
assert(max_allowed_diff > 0);
|
||||
for (j = 0; j < height; ++j) {
|
||||
for (i = 0; i < width; ++i) {
|
||||
const int stride = width * 4;
|
||||
const size_t offset = j * stride + i;
|
||||
if (!PixelsAreSimilar(rgba1[offset], rgba2[offset], max_allowed_diff)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Minimize number of frames by combining successive frames that have at max
|
||||
// 'max_diff' difference per channel between corresponding pixels.
|
||||
static void MinimizeAnimationFrames(AnimatedImage* const img, int max_diff) {
|
||||
uint32_t i;
|
||||
for (i = 1; i < img->num_frames; ++i) {
|
||||
DecodedFrame* const frame1 = &img->frames[i - 1];
|
||||
DecodedFrame* const frame2 = &img->frames[i];
|
||||
const uint8_t* const rgba1 = frame1->rgba;
|
||||
const uint8_t* const rgba2 = frame2->rgba;
|
||||
int should_merge_frames = 0;
|
||||
// If merging frames will result in integer overflow for 'duration',
|
||||
// skip merging.
|
||||
if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
|
||||
if (max_diff > 0) {
|
||||
should_merge_frames = FramesAreSimilar(rgba1, rgba2, img->canvas_width,
|
||||
img->canvas_height, max_diff);
|
||||
} else {
|
||||
should_merge_frames =
|
||||
FramesAreEqual(rgba1, rgba2, img->canvas_width, img->canvas_height);
|
||||
}
|
||||
if (should_merge_frames) { // Merge 'i+1'th frame into 'i'th frame.
|
||||
frame1->duration += frame2->duration;
|
||||
if (i + 1 < img->num_frames) {
|
||||
memmove(&img->frames[i], &img->frames[i + 1],
|
||||
(img->num_frames - i - 1) * sizeof(*img->frames));
|
||||
}
|
||||
--img->num_frames;
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int CompareValues(uint32_t a, uint32_t b, const char* output_str) {
|
||||
if (a != b) {
|
||||
fprintf(stderr, "%s: %d vs %d\n", output_str, a, b);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int CompareBackgroundColor(uint32_t bg1, uint32_t bg2, int premultiply) {
|
||||
if (premultiply) {
|
||||
const int alpha1 = (bg1 >> 24) & 0xff;
|
||||
const int alpha2 = (bg2 >> 24) & 0xff;
|
||||
if (alpha1 == 0 && alpha2 == 0) return 1;
|
||||
}
|
||||
if (bg1 != bg2) {
|
||||
fprintf(stderr, "Background color mismatch: 0x%08x vs 0x%08x\n",
|
||||
bg1, bg2);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Note: As long as frame durations and reconstructed frames are identical, it
|
||||
// is OK for other aspects like offsets, dispose/blend method to vary.
|
||||
static int CompareAnimatedImagePair(const AnimatedImage* const img1,
|
||||
const AnimatedImage* const img2,
|
||||
int premultiply,
|
||||
double min_psnr) {
|
||||
int ok = 1;
|
||||
const int is_multi_frame_image = (img1->num_frames > 1);
|
||||
uint32_t i;
|
||||
|
||||
ok = CompareValues(img1->canvas_width, img2->canvas_width,
|
||||
"Canvas width mismatch") && ok;
|
||||
ok = CompareValues(img1->canvas_height, img2->canvas_height,
|
||||
"Canvas height mismatch") && ok;
|
||||
ok = CompareValues(img1->num_frames, img2->num_frames,
|
||||
"Frame count mismatch") && ok;
|
||||
if (!ok) return 0; // These are fatal failures, can't proceed.
|
||||
|
||||
if (is_multi_frame_image) { // Checks relevant for multi-frame images only.
|
||||
int max_loop_count_workaround = 0;
|
||||
// Transcodes to webp increase the gif loop count by 1 for compatibility.
|
||||
// When the gif has the maximum value the webp value will be off by one.
|
||||
if ((img1->format == ANIM_GIF && img1->loop_count == 65536 &&
|
||||
img2->format == ANIM_WEBP && img2->loop_count == 65535) ||
|
||||
(img1->format == ANIM_WEBP && img1->loop_count == 65535 &&
|
||||
img2->format == ANIM_GIF && img2->loop_count == 65536)) {
|
||||
max_loop_count_workaround = 1;
|
||||
}
|
||||
ok = (max_loop_count_workaround ||
|
||||
CompareValues(img1->loop_count, img2->loop_count,
|
||||
"Loop count mismatch")) && ok;
|
||||
ok = CompareBackgroundColor(img1->bgcolor, img2->bgcolor,
|
||||
premultiply) && ok;
|
||||
}
|
||||
|
||||
for (i = 0; i < img1->num_frames; ++i) {
|
||||
// Pixel-by-pixel comparison.
|
||||
const uint8_t* const rgba1 = img1->frames[i].rgba;
|
||||
const uint8_t* const rgba2 = img2->frames[i].rgba;
|
||||
int max_diff;
|
||||
double psnr;
|
||||
if (is_multi_frame_image) { // Check relevant for multi-frame images only.
|
||||
const char format[] = "Frame #%d, duration mismatch";
|
||||
char tmp[sizeof(format) + 8];
|
||||
ok = ok && (snprintf(tmp, sizeof(tmp), format, i) >= 0);
|
||||
ok = ok && CompareValues(img1->frames[i].duration,
|
||||
img2->frames[i].duration, tmp);
|
||||
}
|
||||
GetDiffAndPSNR(rgba1, rgba2, img1->canvas_width, img1->canvas_height,
|
||||
premultiply, &max_diff, &psnr);
|
||||
if (min_psnr > 0.) {
|
||||
if (psnr < min_psnr) {
|
||||
fprintf(stderr, "Frame #%d, psnr = %.2lf (min_psnr = %f)\n", i,
|
||||
psnr, min_psnr);
|
||||
ok = 0;
|
||||
}
|
||||
} else {
|
||||
if (max_diff != 0) {
|
||||
fprintf(stderr, "Frame #%d, max pixel diff: %d\n", i, max_diff);
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void Help(void) {
|
||||
printf("Usage: anim_diff <image1> <image2> [options]\n");
|
||||
printf("\nOptions:\n");
|
||||
printf(" -dump_frames <folder> dump decoded frames in PAM format\n");
|
||||
printf(" -min_psnr <float> ... minimum per-frame PSNR\n");
|
||||
printf(" -raw_comparison ..... if this flag is not used, RGB is\n");
|
||||
printf(" premultiplied before comparison\n");
|
||||
printf(" -max_diff <int> ..... maximum allowed difference per channel\n"
|
||||
" between corresponding pixels in subsequent\n"
|
||||
" frames\n");
|
||||
printf(" -h .................. this help\n");
|
||||
printf(" -version ............ print version number and exit\n");
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
int return_code = -1;
|
||||
int dump_frames = 0;
|
||||
const char* dump_folder = NULL;
|
||||
double min_psnr = 0.;
|
||||
int got_input1 = 0;
|
||||
int got_input2 = 0;
|
||||
int premultiply = 1;
|
||||
int max_diff = 0;
|
||||
int i, c;
|
||||
const char* files[2] = { NULL, NULL };
|
||||
AnimatedImage images[2];
|
||||
|
||||
INIT_WARGV(argc, argv);
|
||||
|
||||
for (c = 1; c < argc; ++c) {
|
||||
int parse_error = 0;
|
||||
if (!strcmp(argv[c], "-dump_frames")) {
|
||||
if (c < argc - 1) {
|
||||
dump_frames = 1;
|
||||
dump_folder = (const char*)GET_WARGV(argv, ++c);
|
||||
} else {
|
||||
parse_error = 1;
|
||||
}
|
||||
} else if (!strcmp(argv[c], "-min_psnr")) {
|
||||
if (c < argc - 1) {
|
||||
min_psnr = ExUtilGetFloat(argv[++c], &parse_error);
|
||||
} else {
|
||||
parse_error = 1;
|
||||
}
|
||||
} else if (!strcmp(argv[c], "-raw_comparison")) {
|
||||
premultiply = 0;
|
||||
} else if (!strcmp(argv[c], "-max_diff")) {
|
||||
if (c < argc - 1) {
|
||||
max_diff = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else {
|
||||
parse_error = 1;
|
||||
}
|
||||
} else if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else if (!strcmp(argv[c], "-version")) {
|
||||
int dec_version, demux_version;
|
||||
GetAnimatedImageVersions(&dec_version, &demux_version);
|
||||
printf("WebP Decoder version: %d.%d.%d\nWebP Demux version: %d.%d.%d\n",
|
||||
(dec_version >> 16) & 0xff, (dec_version >> 8) & 0xff,
|
||||
(dec_version >> 0) & 0xff,
|
||||
(demux_version >> 16) & 0xff, (demux_version >> 8) & 0xff,
|
||||
(demux_version >> 0) & 0xff);
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else {
|
||||
if (!got_input1) {
|
||||
files[0] = (const char*)GET_WARGV(argv, c);
|
||||
got_input1 = 1;
|
||||
} else if (!got_input2) {
|
||||
files[1] = (const char*)GET_WARGV(argv, c);
|
||||
got_input2 = 1;
|
||||
} else {
|
||||
parse_error = 1;
|
||||
}
|
||||
}
|
||||
if (parse_error) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
}
|
||||
if (argc < 3) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
|
||||
|
||||
if (!got_input2) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
|
||||
if (dump_frames) {
|
||||
WPRINTF("Dumping decoded frames in: %s\n", (const W_CHAR*)dump_folder);
|
||||
}
|
||||
|
||||
memset(images, 0, sizeof(images));
|
||||
for (i = 0; i < 2; ++i) {
|
||||
WPRINTF("Decoding file: %s\n", (const W_CHAR*)files[i]);
|
||||
if (!ReadAnimatedImage(files[i], &images[i], dump_frames, dump_folder)) {
|
||||
WFPRINTF(stderr, "Error decoding file: %s\n Aborting.\n",
|
||||
(const W_CHAR*)files[i]);
|
||||
return_code = -2;
|
||||
goto End;
|
||||
} else {
|
||||
MinimizeAnimationFrames(&images[i], max_diff);
|
||||
}
|
||||
}
|
||||
|
||||
if (!CompareAnimatedImagePair(&images[0], &images[1],
|
||||
premultiply, min_psnr)) {
|
||||
WFPRINTF(stderr, "\nFiles %s and %s differ.\n", (const W_CHAR*)files[0],
|
||||
(const W_CHAR*)files[1]);
|
||||
return_code = -3;
|
||||
} else {
|
||||
WPRINTF("\nFiles %s and %s are identical.\n", (const W_CHAR*)files[0],
|
||||
(const W_CHAR*)files[1]);
|
||||
return_code = 0;
|
||||
}
|
||||
End:
|
||||
ClearAnimatedImage(&images[0]);
|
||||
ClearAnimatedImage(&images[1]);
|
||||
FREE_WARGV_AND_RETURN(return_code);
|
||||
}
|
121
examples/anim_dump.c
Normal file
121
examples/anim_dump.c
Normal file
@@ -0,0 +1,121 @@
|
||||
// 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Decodes an animated WebP file and dumps the decoded frames as PNG or TIFF.
|
||||
//
|
||||
// Author: Skal (pascal.massimino@gmail.com)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h> // for 'strcmp'.
|
||||
|
||||
#include "./anim_util.h"
|
||||
#include "webp/decode.h"
|
||||
#include "../imageio/image_enc.h"
|
||||
#include "./unicode.h"
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
static void Help(void) {
|
||||
printf("Usage: anim_dump [options] files...\n");
|
||||
printf("\nOptions:\n");
|
||||
printf(" -folder <string> .... dump folder (default: '.')\n");
|
||||
printf(" -prefix <string> .... prefix for dumped frames "
|
||||
"(default: 'dump_')\n");
|
||||
printf(" -tiff ............... save frames as TIFF\n");
|
||||
printf(" -pam ................ save frames as PAM\n");
|
||||
printf(" -h .................. this help\n");
|
||||
printf(" -version ............ print version number and exit\n");
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
int error = 0;
|
||||
const W_CHAR* dump_folder = TO_W_CHAR(".");
|
||||
const W_CHAR* prefix = TO_W_CHAR("dump_");
|
||||
const W_CHAR* suffix = TO_W_CHAR("png");
|
||||
WebPOutputFileFormat format = PNG;
|
||||
int c;
|
||||
|
||||
INIT_WARGV(argc, argv);
|
||||
|
||||
if (argc < 2) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
|
||||
for (c = 1; !error && c < argc; ++c) {
|
||||
if (!strcmp(argv[c], "-folder")) {
|
||||
if (c + 1 == argc) {
|
||||
fprintf(stderr, "missing argument after option '%s'\n", argv[c]);
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
dump_folder = GET_WARGV(argv, ++c);
|
||||
} else if (!strcmp(argv[c], "-prefix")) {
|
||||
if (c + 1 == argc) {
|
||||
fprintf(stderr, "missing argument after option '%s'\n", argv[c]);
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
prefix = GET_WARGV(argv, ++c);
|
||||
} else if (!strcmp(argv[c], "-tiff")) {
|
||||
format = TIFF;
|
||||
suffix = TO_W_CHAR("tiff");
|
||||
} else if (!strcmp(argv[c], "-pam")) {
|
||||
format = PAM;
|
||||
suffix = TO_W_CHAR("pam");
|
||||
} else if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else if (!strcmp(argv[c], "-version")) {
|
||||
int dec_version, demux_version;
|
||||
GetAnimatedImageVersions(&dec_version, &demux_version);
|
||||
printf("WebP Decoder version: %d.%d.%d\nWebP Demux version: %d.%d.%d\n",
|
||||
(dec_version >> 16) & 0xff, (dec_version >> 8) & 0xff,
|
||||
(dec_version >> 0) & 0xff,
|
||||
(demux_version >> 16) & 0xff, (demux_version >> 8) & 0xff,
|
||||
(demux_version >> 0) & 0xff);
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else {
|
||||
uint32_t i;
|
||||
AnimatedImage image;
|
||||
const W_CHAR* const file = GET_WARGV(argv, c);
|
||||
memset(&image, 0, sizeof(image));
|
||||
WPRINTF("Decoding file: %s as %s/%sxxxx.%s\n",
|
||||
file, dump_folder, prefix, suffix);
|
||||
if (!ReadAnimatedImage((const char*)file, &image, 0, NULL)) {
|
||||
WFPRINTF(stderr, "Error decoding file: %s\n Aborting.\n", file);
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
for (i = 0; !error && i < image.num_frames; ++i) {
|
||||
W_CHAR out_file[1024];
|
||||
WebPDecBuffer buffer;
|
||||
WebPInitDecBuffer(&buffer);
|
||||
buffer.colorspace = MODE_RGBA;
|
||||
buffer.is_external_memory = 1;
|
||||
buffer.width = image.canvas_width;
|
||||
buffer.height = image.canvas_height;
|
||||
buffer.u.RGBA.rgba = image.frames[i].rgba;
|
||||
buffer.u.RGBA.stride = buffer.width * sizeof(uint32_t);
|
||||
buffer.u.RGBA.size = buffer.u.RGBA.stride * buffer.height;
|
||||
WSNPRINTF(out_file, sizeof(out_file), "%s/%s%.4d.%s",
|
||||
dump_folder, prefix, i, suffix);
|
||||
if (!WebPSaveImage(&buffer, format, (const char*)out_file)) {
|
||||
WFPRINTF(stderr, "Error while saving image '%s'\n", out_file);
|
||||
error = 1;
|
||||
}
|
||||
WebPFreeDecBuffer(&buffer);
|
||||
}
|
||||
ClearAnimatedImage(&image);
|
||||
}
|
||||
}
|
||||
FREE_WARGV_AND_RETURN(error ? 1 : 0);
|
||||
}
|
782
examples/anim_util.c
Normal file
782
examples/anim_util.c
Normal file
@@ -0,0 +1,782 @@
|
||||
// Copyright 2015 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Utilities for animated images
|
||||
|
||||
#include "./anim_util.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(WEBP_HAVE_GIF)
|
||||
#include <gif_lib.h>
|
||||
#endif
|
||||
#include "webp/format_constants.h"
|
||||
#include "webp/decode.h"
|
||||
#include "webp/demux.h"
|
||||
#include "../imageio/imageio_util.h"
|
||||
#include "./gifdec.h"
|
||||
#include "./unicode.h"
|
||||
#include "./unicode_gif.h"
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
static const int kNumChannels = 4;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Common utilities.
|
||||
|
||||
#if defined(WEBP_HAVE_GIF)
|
||||
// Returns true if the frame covers the full canvas.
|
||||
static int IsFullFrame(int width, int height,
|
||||
int canvas_width, int canvas_height) {
|
||||
return (width == canvas_width && height == canvas_height);
|
||||
}
|
||||
#endif // WEBP_HAVE_GIF
|
||||
|
||||
static int CheckSizeForOverflow(uint64_t size) {
|
||||
return (size == (size_t)size);
|
||||
}
|
||||
|
||||
static int AllocateFrames(AnimatedImage* const image, uint32_t num_frames) {
|
||||
uint32_t i;
|
||||
uint8_t* mem = NULL;
|
||||
DecodedFrame* frames = NULL;
|
||||
const uint64_t rgba_size =
|
||||
(uint64_t)image->canvas_width * kNumChannels * image->canvas_height;
|
||||
const uint64_t total_size = (uint64_t)num_frames * rgba_size * sizeof(*mem);
|
||||
const uint64_t total_frame_size = (uint64_t)num_frames * sizeof(*frames);
|
||||
if (!CheckSizeForOverflow(total_size) ||
|
||||
!CheckSizeForOverflow(total_frame_size)) {
|
||||
return 0;
|
||||
}
|
||||
mem = (uint8_t*)WebPMalloc((size_t)total_size);
|
||||
frames = (DecodedFrame*)WebPMalloc((size_t)total_frame_size);
|
||||
|
||||
if (mem == NULL || frames == NULL) {
|
||||
WebPFree(mem);
|
||||
WebPFree(frames);
|
||||
return 0;
|
||||
}
|
||||
WebPFree(image->raw_mem);
|
||||
image->num_frames = num_frames;
|
||||
image->frames = frames;
|
||||
for (i = 0; i < num_frames; ++i) {
|
||||
frames[i].rgba = mem + i * rgba_size;
|
||||
frames[i].duration = 0;
|
||||
frames[i].is_key_frame = 0;
|
||||
}
|
||||
image->raw_mem = mem;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ClearAnimatedImage(AnimatedImage* const image) {
|
||||
if (image != NULL) {
|
||||
WebPFree(image->raw_mem);
|
||||
WebPFree(image->frames);
|
||||
image->num_frames = 0;
|
||||
image->frames = NULL;
|
||||
image->raw_mem = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(WEBP_HAVE_GIF)
|
||||
// Clear the canvas to transparent.
|
||||
static void ZeroFillCanvas(uint8_t* rgba,
|
||||
uint32_t canvas_width, uint32_t canvas_height) {
|
||||
memset(rgba, 0, canvas_width * kNumChannels * canvas_height);
|
||||
}
|
||||
|
||||
// Clear given frame rectangle to transparent.
|
||||
static void ZeroFillFrameRect(uint8_t* rgba, int rgba_stride, int x_offset,
|
||||
int y_offset, int width, int height) {
|
||||
int j;
|
||||
assert(width * kNumChannels <= rgba_stride);
|
||||
rgba += y_offset * rgba_stride + x_offset * kNumChannels;
|
||||
for (j = 0; j < height; ++j) {
|
||||
memset(rgba, 0, width * kNumChannels);
|
||||
rgba += rgba_stride;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy width * height pixels from 'src' to 'dst'.
|
||||
static void CopyCanvas(const uint8_t* src, uint8_t* dst,
|
||||
uint32_t width, uint32_t height) {
|
||||
assert(src != NULL && dst != NULL);
|
||||
memcpy(dst, src, width * kNumChannels * height);
|
||||
}
|
||||
|
||||
// Copy pixels in the given rectangle from 'src' to 'dst' honoring the 'stride'.
|
||||
static void CopyFrameRectangle(const uint8_t* src, uint8_t* dst, int stride,
|
||||
int x_offset, int y_offset,
|
||||
int width, int height) {
|
||||
int j;
|
||||
const int width_in_bytes = width * kNumChannels;
|
||||
const size_t offset = y_offset * stride + x_offset * kNumChannels;
|
||||
assert(width_in_bytes <= stride);
|
||||
src += offset;
|
||||
dst += offset;
|
||||
for (j = 0; j < height; ++j) {
|
||||
memcpy(dst, src, width_in_bytes);
|
||||
src += stride;
|
||||
dst += stride;
|
||||
}
|
||||
}
|
||||
#endif // WEBP_HAVE_GIF
|
||||
|
||||
// Canonicalize all transparent pixels to transparent black to aid comparison.
|
||||
static void CleanupTransparentPixels(uint32_t* rgba,
|
||||
uint32_t width, uint32_t height) {
|
||||
const uint32_t* const rgba_end = rgba + width * height;
|
||||
while (rgba < rgba_end) {
|
||||
const uint8_t alpha = (*rgba >> 24) & 0xff;
|
||||
if (alpha == 0) {
|
||||
*rgba = 0;
|
||||
}
|
||||
++rgba;
|
||||
}
|
||||
}
|
||||
|
||||
// Dump frame to a PAM file. Returns true on success.
|
||||
static int DumpFrame(const char filename[], const char dump_folder[],
|
||||
uint32_t frame_num, const uint8_t rgba[],
|
||||
int canvas_width, int canvas_height) {
|
||||
int ok = 0;
|
||||
size_t max_len;
|
||||
int y;
|
||||
const W_CHAR* base_name = NULL;
|
||||
W_CHAR* file_name = NULL;
|
||||
FILE* f = NULL;
|
||||
const char* row;
|
||||
|
||||
if (dump_folder == NULL) dump_folder = (const char*)TO_W_CHAR(".");
|
||||
|
||||
base_name = WSTRRCHR(filename, '/');
|
||||
base_name = (base_name == NULL) ? (const W_CHAR*)filename : base_name + 1;
|
||||
max_len = WSTRLEN(dump_folder) + 1 + WSTRLEN(base_name)
|
||||
+ strlen("_frame_") + strlen(".pam") + 8;
|
||||
file_name = (W_CHAR*)WebPMalloc(max_len * sizeof(*file_name));
|
||||
if (file_name == NULL) goto End;
|
||||
|
||||
if (WSNPRINTF(file_name, max_len, "%s/%s_frame_%d.pam",
|
||||
(const W_CHAR*)dump_folder, base_name, frame_num) < 0) {
|
||||
fprintf(stderr, "Error while generating file name\n");
|
||||
goto End;
|
||||
}
|
||||
|
||||
f = WFOPEN(file_name, "wb");
|
||||
if (f == NULL) {
|
||||
WFPRINTF(stderr, "Error opening file for writing: %s\n", file_name);
|
||||
ok = 0;
|
||||
goto End;
|
||||
}
|
||||
if (fprintf(f, "P7\nWIDTH %d\nHEIGHT %d\n"
|
||||
"DEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n",
|
||||
canvas_width, canvas_height) < 0) {
|
||||
WFPRINTF(stderr, "Write error for file %s\n", file_name);
|
||||
goto End;
|
||||
}
|
||||
row = (const char*)rgba;
|
||||
for (y = 0; y < canvas_height; ++y) {
|
||||
if (fwrite(row, canvas_width * kNumChannels, 1, f) != 1) {
|
||||
WFPRINTF(stderr, "Error writing to file: %s\n", file_name);
|
||||
goto End;
|
||||
}
|
||||
row += canvas_width * kNumChannels;
|
||||
}
|
||||
ok = 1;
|
||||
End:
|
||||
if (f != NULL) fclose(f);
|
||||
WebPFree(file_name);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// WebP Decoding.
|
||||
|
||||
// Returns true if this is a valid WebP bitstream.
|
||||
static int IsWebP(const WebPData* const webp_data) {
|
||||
return (WebPGetInfo(webp_data->bytes, webp_data->size, NULL, NULL) != 0);
|
||||
}
|
||||
|
||||
// Read animated WebP bitstream 'webp_data' into 'AnimatedImage' struct.
|
||||
static int ReadAnimatedWebP(const char filename[],
|
||||
const WebPData* const webp_data,
|
||||
AnimatedImage* const image, int dump_frames,
|
||||
const char dump_folder[]) {
|
||||
int ok = 0;
|
||||
int dump_ok = 1;
|
||||
uint32_t frame_index = 0;
|
||||
int prev_frame_timestamp = 0;
|
||||
WebPAnimDecoder* dec;
|
||||
WebPAnimInfo anim_info;
|
||||
|
||||
memset(image, 0, sizeof(*image));
|
||||
|
||||
dec = WebPAnimDecoderNew(webp_data, NULL);
|
||||
if (dec == NULL) {
|
||||
WFPRINTF(stderr, "Error parsing image: %s\n", (const W_CHAR*)filename);
|
||||
goto End;
|
||||
}
|
||||
|
||||
if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
|
||||
fprintf(stderr, "Error getting global info about the animation\n");
|
||||
goto End;
|
||||
}
|
||||
|
||||
// Animation properties.
|
||||
image->canvas_width = anim_info.canvas_width;
|
||||
image->canvas_height = anim_info.canvas_height;
|
||||
image->loop_count = anim_info.loop_count;
|
||||
image->bgcolor = anim_info.bgcolor;
|
||||
|
||||
// Allocate frames.
|
||||
if (!AllocateFrames(image, anim_info.frame_count)) goto End;
|
||||
|
||||
// Decode frames.
|
||||
while (WebPAnimDecoderHasMoreFrames(dec)) {
|
||||
DecodedFrame* curr_frame;
|
||||
uint8_t* curr_rgba;
|
||||
uint8_t* frame_rgba;
|
||||
int timestamp;
|
||||
|
||||
if (!WebPAnimDecoderGetNext(dec, &frame_rgba, ×tamp)) {
|
||||
fprintf(stderr, "Error decoding frame #%u\n", frame_index);
|
||||
goto End;
|
||||
}
|
||||
assert(frame_index < anim_info.frame_count);
|
||||
curr_frame = &image->frames[frame_index];
|
||||
curr_rgba = curr_frame->rgba;
|
||||
curr_frame->duration = timestamp - prev_frame_timestamp;
|
||||
curr_frame->is_key_frame = 0; // Unused.
|
||||
memcpy(curr_rgba, frame_rgba,
|
||||
image->canvas_width * kNumChannels * image->canvas_height);
|
||||
|
||||
// Needed only because we may want to compare with GIF later.
|
||||
CleanupTransparentPixels((uint32_t*)curr_rgba,
|
||||
image->canvas_width, image->canvas_height);
|
||||
|
||||
if (dump_frames && dump_ok) {
|
||||
dump_ok = DumpFrame(filename, dump_folder, frame_index, curr_rgba,
|
||||
image->canvas_width, image->canvas_height);
|
||||
if (!dump_ok) { // Print error once, but continue decode loop.
|
||||
fprintf(stderr, "Error dumping frames to %s\n", dump_folder);
|
||||
}
|
||||
}
|
||||
|
||||
++frame_index;
|
||||
prev_frame_timestamp = timestamp;
|
||||
}
|
||||
ok = dump_ok;
|
||||
if (ok) image->format = ANIM_WEBP;
|
||||
|
||||
End:
|
||||
WebPAnimDecoderDelete(dec);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// GIF Decoding.
|
||||
|
||||
#if defined(WEBP_HAVE_GIF)
|
||||
|
||||
// Returns true if this is a valid GIF bitstream.
|
||||
static int IsGIF(const WebPData* const data) {
|
||||
return data->size > GIF_STAMP_LEN &&
|
||||
(!memcmp(GIF_STAMP, data->bytes, GIF_STAMP_LEN) ||
|
||||
!memcmp(GIF87_STAMP, data->bytes, GIF_STAMP_LEN) ||
|
||||
!memcmp(GIF89_STAMP, data->bytes, GIF_STAMP_LEN));
|
||||
}
|
||||
|
||||
// GIFLIB_MAJOR is only defined in libgif >= 4.2.0.
|
||||
#if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR)
|
||||
# define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR)
|
||||
# define LOCAL_GIF_PREREQ(maj, min) \
|
||||
(LOCAL_GIF_VERSION >= (((maj) << 8) | (min)))
|
||||
#else
|
||||
# define LOCAL_GIF_VERSION 0
|
||||
# define LOCAL_GIF_PREREQ(maj, min) 0
|
||||
#endif
|
||||
|
||||
#if !LOCAL_GIF_PREREQ(5, 0)
|
||||
|
||||
// Added in v5.0
|
||||
typedef struct {
|
||||
int DisposalMode;
|
||||
#define DISPOSAL_UNSPECIFIED 0 // No disposal specified
|
||||
#define DISPOSE_DO_NOT 1 // Leave image in place
|
||||
#define DISPOSE_BACKGROUND 2 // Set area to background color
|
||||
#define DISPOSE_PREVIOUS 3 // Restore to previous content
|
||||
int UserInputFlag; // User confirmation required before disposal
|
||||
int DelayTime; // Pre-display delay in 0.01sec units
|
||||
int TransparentColor; // Palette index for transparency, -1 if none
|
||||
#define NO_TRANSPARENT_COLOR -1
|
||||
} GraphicsControlBlock;
|
||||
|
||||
static int DGifExtensionToGCB(const size_t GifExtensionLength,
|
||||
const GifByteType* GifExtension,
|
||||
GraphicsControlBlock* gcb) {
|
||||
if (GifExtensionLength != 4) {
|
||||
return GIF_ERROR;
|
||||
}
|
||||
gcb->DisposalMode = (GifExtension[0] >> 2) & 0x07;
|
||||
gcb->UserInputFlag = (GifExtension[0] & 0x02) != 0;
|
||||
gcb->DelayTime = GifExtension[1] | (GifExtension[2] << 8);
|
||||
if (GifExtension[0] & 0x01) {
|
||||
gcb->TransparentColor = (int)GifExtension[3];
|
||||
} else {
|
||||
gcb->TransparentColor = NO_TRANSPARENT_COLOR;
|
||||
}
|
||||
return GIF_OK;
|
||||
}
|
||||
|
||||
static int DGifSavedExtensionToGCB(GifFileType* GifFile, int ImageIndex,
|
||||
GraphicsControlBlock* gcb) {
|
||||
int i;
|
||||
if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) {
|
||||
return GIF_ERROR;
|
||||
}
|
||||
gcb->DisposalMode = DISPOSAL_UNSPECIFIED;
|
||||
gcb->UserInputFlag = 0;
|
||||
gcb->DelayTime = 0;
|
||||
gcb->TransparentColor = NO_TRANSPARENT_COLOR;
|
||||
|
||||
for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) {
|
||||
ExtensionBlock* ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
|
||||
if (ep->Function == GRAPHICS_EXT_FUNC_CODE) {
|
||||
return DGifExtensionToGCB(
|
||||
ep->ByteCount, (const GifByteType*)ep->Bytes, gcb);
|
||||
}
|
||||
}
|
||||
return GIF_ERROR;
|
||||
}
|
||||
|
||||
#define CONTINUE_EXT_FUNC_CODE 0x00
|
||||
|
||||
// Signature was changed in v5.0
|
||||
#define DGifOpenFileName(a, b) DGifOpenFileName(a)
|
||||
|
||||
#endif // !LOCAL_GIF_PREREQ(5, 0)
|
||||
|
||||
// Signature changed in v5.1
|
||||
#if !LOCAL_GIF_PREREQ(5, 1)
|
||||
#define DGifCloseFile(a, b) DGifCloseFile(a)
|
||||
#endif
|
||||
|
||||
static int IsKeyFrameGIF(const GifImageDesc* prev_desc, int prev_dispose,
|
||||
const DecodedFrame* const prev_frame,
|
||||
int canvas_width, int canvas_height) {
|
||||
if (prev_frame == NULL) return 1;
|
||||
if (prev_dispose == DISPOSE_BACKGROUND) {
|
||||
if (IsFullFrame(prev_desc->Width, prev_desc->Height,
|
||||
canvas_width, canvas_height)) {
|
||||
return 1;
|
||||
}
|
||||
if (prev_frame->is_key_frame) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int GetTransparentIndexGIF(GifFileType* gif) {
|
||||
GraphicsControlBlock first_gcb;
|
||||
memset(&first_gcb, 0, sizeof(first_gcb));
|
||||
DGifSavedExtensionToGCB(gif, 0, &first_gcb);
|
||||
return first_gcb.TransparentColor;
|
||||
}
|
||||
|
||||
static uint32_t GetBackgroundColorGIF(GifFileType* gif) {
|
||||
const int transparent_index = GetTransparentIndexGIF(gif);
|
||||
const ColorMapObject* const color_map = gif->SColorMap;
|
||||
if (transparent_index != NO_TRANSPARENT_COLOR &&
|
||||
gif->SBackGroundColor == transparent_index) {
|
||||
return 0x00000000; // Special case: transparent black.
|
||||
} else if (color_map == NULL || color_map->Colors == NULL
|
||||
|| gif->SBackGroundColor >= color_map->ColorCount) {
|
||||
return 0xffffffff; // Invalid: assume white.
|
||||
} else {
|
||||
const GifColorType color = color_map->Colors[gif->SBackGroundColor];
|
||||
return (0xffu << 24) |
|
||||
(color.Red << 16) |
|
||||
(color.Green << 8) |
|
||||
(color.Blue << 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Find appropriate app extension and get loop count from the next extension.
|
||||
// We use Chrome's interpretation of the 'loop_count' semantics:
|
||||
// if not present -> loop once
|
||||
// if present and loop_count == 0, return 0 ('infinite').
|
||||
// if present and loop_count != 0, it's the number of *extra* loops
|
||||
// so we need to return loop_count + 1 as total loop number.
|
||||
static uint32_t GetLoopCountGIF(const GifFileType* const gif) {
|
||||
int i;
|
||||
for (i = 0; i < gif->ImageCount; ++i) {
|
||||
const SavedImage* const image = &gif->SavedImages[i];
|
||||
int j;
|
||||
for (j = 0; (j + 1) < image->ExtensionBlockCount; ++j) {
|
||||
const ExtensionBlock* const eb1 = image->ExtensionBlocks + j;
|
||||
const ExtensionBlock* const eb2 = image->ExtensionBlocks + j + 1;
|
||||
const char* const signature = (const char*)eb1->Bytes;
|
||||
const int signature_is_ok =
|
||||
(eb1->Function == APPLICATION_EXT_FUNC_CODE) &&
|
||||
(eb1->ByteCount == 11) &&
|
||||
(!memcmp(signature, "NETSCAPE2.0", 11) ||
|
||||
!memcmp(signature, "ANIMEXTS1.0", 11));
|
||||
if (signature_is_ok &&
|
||||
eb2->Function == CONTINUE_EXT_FUNC_CODE && eb2->ByteCount >= 3 &&
|
||||
eb2->Bytes[0] == 1) {
|
||||
const uint32_t extra_loop = ((uint32_t)(eb2->Bytes[2]) << 8) +
|
||||
((uint32_t)(eb2->Bytes[1]) << 0);
|
||||
return (extra_loop > 0) ? extra_loop + 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1; // Default.
|
||||
}
|
||||
|
||||
// Get duration of 'n'th frame in milliseconds.
|
||||
static int GetFrameDurationGIF(GifFileType* gif, int n) {
|
||||
GraphicsControlBlock gcb;
|
||||
memset(&gcb, 0, sizeof(gcb));
|
||||
DGifSavedExtensionToGCB(gif, n, &gcb);
|
||||
return gcb.DelayTime * 10;
|
||||
}
|
||||
|
||||
// Returns true if frame 'target' completely covers 'covered'.
|
||||
static int CoversFrameGIF(const GifImageDesc* const target,
|
||||
const GifImageDesc* const covered) {
|
||||
return target->Left <= covered->Left &&
|
||||
covered->Left + covered->Width <= target->Left + target->Width &&
|
||||
target->Top <= covered->Top &&
|
||||
covered->Top + covered->Height <= target->Top + target->Height;
|
||||
}
|
||||
|
||||
static void RemapPixelsGIF(const uint8_t* const src,
|
||||
const ColorMapObject* const cmap,
|
||||
int transparent_color, int len, uint8_t* dst) {
|
||||
int i;
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (src[i] != transparent_color) {
|
||||
// If a pixel in the current frame is transparent, we don't modify it, so
|
||||
// that we can see-through the corresponding pixel from an earlier frame.
|
||||
const GifColorType c = cmap->Colors[src[i]];
|
||||
dst[4 * i + 0] = c.Red;
|
||||
dst[4 * i + 1] = c.Green;
|
||||
dst[4 * i + 2] = c.Blue;
|
||||
dst[4 * i + 3] = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int ReadFrameGIF(const SavedImage* const gif_image,
|
||||
const ColorMapObject* cmap, int transparent_color,
|
||||
int out_stride, uint8_t* const dst) {
|
||||
const GifImageDesc* image_desc = &gif_image->ImageDesc;
|
||||
const uint8_t* in;
|
||||
uint8_t* out;
|
||||
int j;
|
||||
|
||||
if (image_desc->ColorMap) cmap = image_desc->ColorMap;
|
||||
|
||||
if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
|
||||
fprintf(stderr, "Potentially corrupt color map.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
in = (const uint8_t*)gif_image->RasterBits;
|
||||
out = dst + image_desc->Top * out_stride + image_desc->Left * kNumChannels;
|
||||
|
||||
for (j = 0; j < image_desc->Height; ++j) {
|
||||
RemapPixelsGIF(in, cmap, transparent_color, image_desc->Width, out);
|
||||
in += image_desc->Width;
|
||||
out += out_stride;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read animated GIF bitstream from 'filename' into 'AnimatedImage' struct.
|
||||
static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
|
||||
int dump_frames, const char dump_folder[]) {
|
||||
uint32_t frame_count;
|
||||
uint32_t canvas_width, canvas_height;
|
||||
uint32_t i;
|
||||
int gif_error;
|
||||
GifFileType* gif;
|
||||
|
||||
gif = DGifOpenFileUnicode((const W_CHAR*)filename, NULL);
|
||||
if (gif == NULL) {
|
||||
WFPRINTF(stderr, "Could not read file: %s.\n", (const W_CHAR*)filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
gif_error = DGifSlurp(gif);
|
||||
if (gif_error != GIF_OK) {
|
||||
WFPRINTF(stderr, "Could not parse image: %s.\n", (const W_CHAR*)filename);
|
||||
GIFDisplayError(gif, gif_error);
|
||||
DGifCloseFile(gif, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Animation properties.
|
||||
image->canvas_width = (uint32_t)gif->SWidth;
|
||||
image->canvas_height = (uint32_t)gif->SHeight;
|
||||
if (image->canvas_width > MAX_CANVAS_SIZE ||
|
||||
image->canvas_height > MAX_CANVAS_SIZE) {
|
||||
fprintf(stderr, "Invalid canvas dimension: %d x %d\n",
|
||||
image->canvas_width, image->canvas_height);
|
||||
DGifCloseFile(gif, NULL);
|
||||
return 0;
|
||||
}
|
||||
image->loop_count = GetLoopCountGIF(gif);
|
||||
image->bgcolor = GetBackgroundColorGIF(gif);
|
||||
|
||||
frame_count = (uint32_t)gif->ImageCount;
|
||||
if (frame_count == 0) {
|
||||
DGifCloseFile(gif, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (image->canvas_width == 0 || image->canvas_height == 0) {
|
||||
image->canvas_width = gif->SavedImages[0].ImageDesc.Width;
|
||||
image->canvas_height = gif->SavedImages[0].ImageDesc.Height;
|
||||
gif->SavedImages[0].ImageDesc.Left = 0;
|
||||
gif->SavedImages[0].ImageDesc.Top = 0;
|
||||
if (image->canvas_width == 0 || image->canvas_height == 0) {
|
||||
fprintf(stderr, "Invalid canvas size in GIF.\n");
|
||||
DGifCloseFile(gif, NULL);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Allocate frames.
|
||||
if (!AllocateFrames(image, frame_count)) {
|
||||
DGifCloseFile(gif, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
canvas_width = image->canvas_width;
|
||||
canvas_height = image->canvas_height;
|
||||
|
||||
// Decode and reconstruct frames.
|
||||
for (i = 0; i < frame_count; ++i) {
|
||||
const int canvas_width_in_bytes = canvas_width * kNumChannels;
|
||||
const SavedImage* const curr_gif_image = &gif->SavedImages[i];
|
||||
GraphicsControlBlock curr_gcb;
|
||||
DecodedFrame* curr_frame;
|
||||
uint8_t* curr_rgba;
|
||||
|
||||
memset(&curr_gcb, 0, sizeof(curr_gcb));
|
||||
DGifSavedExtensionToGCB(gif, i, &curr_gcb);
|
||||
|
||||
curr_frame = &image->frames[i];
|
||||
curr_rgba = curr_frame->rgba;
|
||||
curr_frame->duration = GetFrameDurationGIF(gif, i);
|
||||
// Force frames with a small or no duration to 100ms to be consistent
|
||||
// with web browsers and other transcoding tools (like gif2webp itself).
|
||||
if (curr_frame->duration <= 10) curr_frame->duration = 100;
|
||||
|
||||
if (i == 0) { // Initialize as transparent.
|
||||
curr_frame->is_key_frame = 1;
|
||||
ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
|
||||
} else {
|
||||
DecodedFrame* const prev_frame = &image->frames[i - 1];
|
||||
const GifImageDesc* const prev_desc = &gif->SavedImages[i - 1].ImageDesc;
|
||||
GraphicsControlBlock prev_gcb;
|
||||
memset(&prev_gcb, 0, sizeof(prev_gcb));
|
||||
DGifSavedExtensionToGCB(gif, i - 1, &prev_gcb);
|
||||
|
||||
curr_frame->is_key_frame =
|
||||
IsKeyFrameGIF(prev_desc, prev_gcb.DisposalMode, prev_frame,
|
||||
canvas_width, canvas_height);
|
||||
|
||||
if (curr_frame->is_key_frame) { // Initialize as transparent.
|
||||
ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
|
||||
} else {
|
||||
int prev_frame_disposed, curr_frame_opaque;
|
||||
int prev_frame_completely_covered;
|
||||
// Initialize with previous canvas.
|
||||
uint8_t* const prev_rgba = image->frames[i - 1].rgba;
|
||||
CopyCanvas(prev_rgba, curr_rgba, canvas_width, canvas_height);
|
||||
|
||||
// Dispose previous frame rectangle.
|
||||
prev_frame_disposed =
|
||||
(prev_gcb.DisposalMode == DISPOSE_BACKGROUND ||
|
||||
prev_gcb.DisposalMode == DISPOSE_PREVIOUS);
|
||||
curr_frame_opaque =
|
||||
(curr_gcb.TransparentColor == NO_TRANSPARENT_COLOR);
|
||||
prev_frame_completely_covered =
|
||||
curr_frame_opaque &&
|
||||
CoversFrameGIF(&curr_gif_image->ImageDesc, prev_desc);
|
||||
|
||||
if (prev_frame_disposed && !prev_frame_completely_covered) {
|
||||
switch (prev_gcb.DisposalMode) {
|
||||
case DISPOSE_BACKGROUND: {
|
||||
ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
|
||||
prev_desc->Left, prev_desc->Top,
|
||||
prev_desc->Width, prev_desc->Height);
|
||||
break;
|
||||
}
|
||||
case DISPOSE_PREVIOUS: {
|
||||
int src_frame_num = i - 2;
|
||||
while (src_frame_num >= 0) {
|
||||
GraphicsControlBlock src_frame_gcb;
|
||||
memset(&src_frame_gcb, 0, sizeof(src_frame_gcb));
|
||||
DGifSavedExtensionToGCB(gif, src_frame_num, &src_frame_gcb);
|
||||
if (src_frame_gcb.DisposalMode != DISPOSE_PREVIOUS) break;
|
||||
--src_frame_num;
|
||||
}
|
||||
if (src_frame_num >= 0) {
|
||||
// Restore pixels inside previous frame rectangle to
|
||||
// corresponding pixels in source canvas.
|
||||
uint8_t* const src_frame_rgba =
|
||||
image->frames[src_frame_num].rgba;
|
||||
CopyFrameRectangle(src_frame_rgba, curr_rgba,
|
||||
canvas_width_in_bytes,
|
||||
prev_desc->Left, prev_desc->Top,
|
||||
prev_desc->Width, prev_desc->Height);
|
||||
} else {
|
||||
// Source canvas doesn't exist. So clear previous frame
|
||||
// rectangle to background.
|
||||
ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
|
||||
prev_desc->Left, prev_desc->Top,
|
||||
prev_desc->Width, prev_desc->Height);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break; // Nothing to do.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decode current frame.
|
||||
if (!ReadFrameGIF(curr_gif_image, gif->SColorMap, curr_gcb.TransparentColor,
|
||||
canvas_width_in_bytes, curr_rgba)) {
|
||||
DGifCloseFile(gif, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (dump_frames) {
|
||||
if (!DumpFrame(filename, dump_folder, i, curr_rgba,
|
||||
canvas_width, canvas_height)) {
|
||||
DGifCloseFile(gif, NULL);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
image->format = ANIM_GIF;
|
||||
DGifCloseFile(gif, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static int IsGIF(const WebPData* const data) {
|
||||
(void)data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
|
||||
int dump_frames, const char dump_folder[]) {
|
||||
(void)filename;
|
||||
(void)image;
|
||||
(void)dump_frames;
|
||||
(void)dump_folder;
|
||||
fprintf(stderr, "GIF support not compiled. Please install the libgif-dev "
|
||||
"package before building.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // WEBP_HAVE_GIF
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
int ReadAnimatedImage(const char filename[], AnimatedImage* const image,
|
||||
int dump_frames, const char dump_folder[]) {
|
||||
int ok = 0;
|
||||
WebPData webp_data;
|
||||
|
||||
WebPDataInit(&webp_data);
|
||||
memset(image, 0, sizeof(*image));
|
||||
|
||||
if (!ImgIoUtilReadFile(filename, &webp_data.bytes, &webp_data.size)) {
|
||||
WFPRINTF(stderr, "Error reading file: %s\n", (const W_CHAR*)filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (IsWebP(&webp_data)) {
|
||||
ok = ReadAnimatedWebP(filename, &webp_data, image, dump_frames,
|
||||
dump_folder);
|
||||
} else if (IsGIF(&webp_data)) {
|
||||
ok = ReadAnimatedGIF(filename, image, dump_frames, dump_folder);
|
||||
} else {
|
||||
WFPRINTF(stderr,
|
||||
"Unknown file type: %s. Supported file types are WebP and GIF\n",
|
||||
(const W_CHAR*)filename);
|
||||
ok = 0;
|
||||
}
|
||||
if (!ok) ClearAnimatedImage(image);
|
||||
WebPDataClear(&webp_data);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void Accumulate(double v1, double v2, double* const max_diff,
|
||||
double* const sse) {
|
||||
const double diff = fabs(v1 - v2);
|
||||
if (diff > *max_diff) *max_diff = diff;
|
||||
*sse += diff * diff;
|
||||
}
|
||||
|
||||
void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
|
||||
uint32_t width, uint32_t height, int premultiply,
|
||||
int* const max_diff, double* const psnr) {
|
||||
const uint32_t stride = width * kNumChannels;
|
||||
const int kAlphaChannel = kNumChannels - 1;
|
||||
double f_max_diff = 0.;
|
||||
double sse = 0.;
|
||||
uint32_t x, y;
|
||||
for (y = 0; y < height; ++y) {
|
||||
for (x = 0; x < stride; x += kNumChannels) {
|
||||
int k;
|
||||
const size_t offset = (size_t)y * stride + x;
|
||||
const int alpha1 = rgba1[offset + kAlphaChannel];
|
||||
const int alpha2 = rgba2[offset + kAlphaChannel];
|
||||
Accumulate(alpha1, alpha2, &f_max_diff, &sse);
|
||||
if (!premultiply) {
|
||||
for (k = 0; k < kAlphaChannel; ++k) {
|
||||
Accumulate(rgba1[offset + k], rgba2[offset + k], &f_max_diff, &sse);
|
||||
}
|
||||
} else {
|
||||
// premultiply R/G/B channels with alpha value
|
||||
for (k = 0; k < kAlphaChannel; ++k) {
|
||||
Accumulate(rgba1[offset + k] * alpha1 / 255.,
|
||||
rgba2[offset + k] * alpha2 / 255.,
|
||||
&f_max_diff, &sse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*max_diff = (int)f_max_diff;
|
||||
if (*max_diff == 0) {
|
||||
*psnr = 99.; // PSNR when images are identical.
|
||||
} else {
|
||||
sse /= stride * height;
|
||||
*psnr = 4.3429448 * log(255. * 255. / sse);
|
||||
}
|
||||
}
|
||||
|
||||
void GetAnimatedImageVersions(int* const decoder_version,
|
||||
int* const demux_version) {
|
||||
*decoder_version = WebPGetDecoderVersion();
|
||||
*demux_version = WebPGetDemuxVersion();
|
||||
}
|
73
examples/anim_util.h
Normal file
73
examples/anim_util.h
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2015 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Utilities for animated images
|
||||
|
||||
#ifndef WEBP_EXAMPLES_ANIM_UTIL_H_
|
||||
#define WEBP_EXAMPLES_ANIM_UTIL_H_
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "webp/config.h"
|
||||
#endif
|
||||
|
||||
#include "webp/types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
ANIM_GIF,
|
||||
ANIM_WEBP
|
||||
} AnimatedFileFormat;
|
||||
|
||||
typedef struct {
|
||||
uint8_t* rgba; // Decoded and reconstructed full frame.
|
||||
int duration; // Frame duration in milliseconds.
|
||||
int is_key_frame; // True if this frame is a key-frame.
|
||||
} DecodedFrame;
|
||||
|
||||
typedef struct {
|
||||
AnimatedFileFormat format;
|
||||
uint32_t canvas_width;
|
||||
uint32_t canvas_height;
|
||||
uint32_t bgcolor;
|
||||
uint32_t loop_count;
|
||||
DecodedFrame* frames;
|
||||
uint32_t num_frames;
|
||||
void* raw_mem;
|
||||
} AnimatedImage;
|
||||
|
||||
// Deallocate everything in 'image' (but not the object itself).
|
||||
void ClearAnimatedImage(AnimatedImage* const image);
|
||||
|
||||
// Read animated image file into 'AnimatedImage' struct.
|
||||
// If 'dump_frames' is true, dump frames to 'dump_folder'.
|
||||
// Previous content of 'image' is obliterated.
|
||||
// Upon successful return, content of 'image' must be deleted by
|
||||
// calling 'ClearAnimatedImage'.
|
||||
int ReadAnimatedImage(const char filename[], AnimatedImage* const image,
|
||||
int dump_frames, const char dump_folder[]);
|
||||
|
||||
// Given two RGBA buffers, calculate max pixel difference and PSNR.
|
||||
// If 'premultiply' is true, R/G/B values will be pre-multiplied by the
|
||||
// transparency before comparison.
|
||||
void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
|
||||
uint32_t width, uint32_t height, int premultiply,
|
||||
int* const max_diff, double* const psnr);
|
||||
|
||||
// Return library versions used by anim_util.
|
||||
void GetAnimatedImageVersions(int* const decoder_version,
|
||||
int* const demux_version);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // WEBP_EXAMPLES_ANIM_UTIL_H_
|
1245
examples/cwebp.c
Normal file
1245
examples/cwebp.c
Normal file
File diff suppressed because it is too large
Load Diff
421
examples/dwebp.c
Normal file
421
examples/dwebp.c
Normal file
@@ -0,0 +1,421 @@
|
||||
// Copyright 2010 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 for decoding a WebP image.
|
||||
//
|
||||
// Author: Skal (pascal.massimino@gmail.com)
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "webp/config.h"
|
||||
#endif
|
||||
|
||||
#include "../examples/example_util.h"
|
||||
#include "../imageio/image_enc.h"
|
||||
#include "../imageio/webpdec.h"
|
||||
#include "./stopwatch.h"
|
||||
#include "./unicode.h"
|
||||
|
||||
static int verbose = 0;
|
||||
static int quiet = 0;
|
||||
#ifndef WEBP_DLL
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern void* VP8GetCPUInfo; // opaque forward declaration.
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
#endif // WEBP_DLL
|
||||
|
||||
|
||||
static int SaveOutput(const WebPDecBuffer* const buffer,
|
||||
WebPOutputFileFormat format, const char* const out_file) {
|
||||
const int use_stdout = (out_file != NULL) && !WSTRCMP(out_file, "-");
|
||||
int ok = 1;
|
||||
Stopwatch stop_watch;
|
||||
|
||||
if (verbose) {
|
||||
StopwatchReset(&stop_watch);
|
||||
}
|
||||
ok = WebPSaveImage(buffer, format, out_file);
|
||||
|
||||
if (ok) {
|
||||
if (!quiet) {
|
||||
if (use_stdout) {
|
||||
fprintf(stderr, "Saved to stdout\n");
|
||||
} else {
|
||||
WFPRINTF(stderr, "Saved file %s\n", (const W_CHAR*)out_file);
|
||||
}
|
||||
}
|
||||
if (verbose) {
|
||||
const double write_time = StopwatchReadAndReset(&stop_watch);
|
||||
fprintf(stderr, "Time to write output: %.3fs\n", write_time);
|
||||
}
|
||||
} else {
|
||||
if (use_stdout) {
|
||||
fprintf(stderr, "Error writing to stdout !!\n");
|
||||
} else {
|
||||
WFPRINTF(stderr, "Error writing file %s !!\n", (const W_CHAR*)out_file);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void Help(void) {
|
||||
printf("Usage: dwebp in_file [options] [-o out_file]\n\n"
|
||||
"Decodes the WebP image file to PNG format [Default].\n"
|
||||
"Note: Animated WebP files are not supported.\n\n"
|
||||
"Use following options to convert into alternate image formats:\n"
|
||||
" -pam ......... save the raw RGBA samples as a color PAM\n"
|
||||
" -ppm ......... save the raw RGB samples as a color PPM\n"
|
||||
" -bmp ......... save as uncompressed BMP format\n"
|
||||
" -tiff ........ save as uncompressed TIFF format\n"
|
||||
" -pgm ......... save the raw YUV samples as a grayscale PGM\n"
|
||||
" file with IMC4 layout\n"
|
||||
" -yuv ......... save the raw YUV samples in flat layout\n"
|
||||
"\n"
|
||||
" Other options are:\n"
|
||||
" -version ..... print version number and exit\n"
|
||||
" -nofancy ..... don't use the fancy YUV420 upscaler\n"
|
||||
" -nofilter .... disable in-loop filtering\n"
|
||||
" -nodither .... disable dithering\n"
|
||||
" -dither <d> .. dithering strength (in 0..100)\n"
|
||||
" -alpha_dither use alpha-plane dithering if needed\n"
|
||||
" -mt .......... use multi-threading\n"
|
||||
" -crop <x> <y> <w> <h> ... crop output with the given rectangle\n"
|
||||
" -resize <w> <h> ......... resize output (*after* any cropping)\n"
|
||||
" -flip ........ flip the output vertically\n"
|
||||
" -alpha ....... only save the alpha plane\n"
|
||||
" -incremental . use incremental decoding (useful for tests)\n"
|
||||
" -h ........... this help message\n"
|
||||
" -v ........... verbose (e.g. print encoding/decoding times)\n"
|
||||
" -quiet ....... quiet mode, don't print anything\n"
|
||||
#ifndef WEBP_DLL
|
||||
" -noasm ....... disable all assembly optimizations\n"
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
static const char* const kFormatType[] = {
|
||||
"unspecified", "lossy", "lossless"
|
||||
};
|
||||
|
||||
static uint8_t* AllocateExternalBuffer(WebPDecoderConfig* config,
|
||||
WebPOutputFileFormat format,
|
||||
int use_external_memory) {
|
||||
uint8_t* external_buffer = NULL;
|
||||
WebPDecBuffer* const output_buffer = &config->output;
|
||||
int w = config->input.width;
|
||||
int h = config->input.height;
|
||||
if (config->options.use_scaling) {
|
||||
w = config->options.scaled_width;
|
||||
h = config->options.scaled_height;
|
||||
} else if (config->options.use_cropping) {
|
||||
w = config->options.crop_width;
|
||||
h = config->options.crop_height;
|
||||
}
|
||||
if (format >= RGB && format <= rgbA_4444) {
|
||||
const int bpp = (format == RGB || format == BGR) ? 3
|
||||
: (format == RGBA_4444 || format == rgbA_4444 ||
|
||||
format == RGB_565) ? 2
|
||||
: 4;
|
||||
uint32_t stride = bpp * w + 7; // <- just for exercising
|
||||
external_buffer = (uint8_t*)WebPMalloc(stride * h);
|
||||
if (external_buffer == NULL) return NULL;
|
||||
output_buffer->u.RGBA.stride = stride;
|
||||
output_buffer->u.RGBA.size = stride * h;
|
||||
output_buffer->u.RGBA.rgba = external_buffer;
|
||||
} else { // YUV and YUVA
|
||||
const int has_alpha = WebPIsAlphaMode(output_buffer->colorspace);
|
||||
uint8_t* tmp;
|
||||
uint32_t stride = w + 3;
|
||||
uint32_t uv_stride = (w + 1) / 2 + 13;
|
||||
uint32_t total_size = stride * h * (has_alpha ? 2 : 1)
|
||||
+ 2 * uv_stride * (h + 1) / 2;
|
||||
assert(format >= YUV && format <= YUVA);
|
||||
external_buffer = (uint8_t*)WebPMalloc(total_size);
|
||||
if (external_buffer == NULL) return NULL;
|
||||
tmp = external_buffer;
|
||||
output_buffer->u.YUVA.y = tmp;
|
||||
output_buffer->u.YUVA.y_stride = stride;
|
||||
output_buffer->u.YUVA.y_size = stride * h;
|
||||
tmp += output_buffer->u.YUVA.y_size;
|
||||
if (has_alpha) {
|
||||
output_buffer->u.YUVA.a = tmp;
|
||||
output_buffer->u.YUVA.a_stride = stride;
|
||||
output_buffer->u.YUVA.a_size = stride * h;
|
||||
tmp += output_buffer->u.YUVA.a_size;
|
||||
} else {
|
||||
output_buffer->u.YUVA.a = NULL;
|
||||
output_buffer->u.YUVA.a_stride = 0;
|
||||
}
|
||||
output_buffer->u.YUVA.u = tmp;
|
||||
output_buffer->u.YUVA.u_stride = uv_stride;
|
||||
output_buffer->u.YUVA.u_size = uv_stride * (h + 1) / 2;
|
||||
tmp += output_buffer->u.YUVA.u_size;
|
||||
|
||||
output_buffer->u.YUVA.v = tmp;
|
||||
output_buffer->u.YUVA.v_stride = uv_stride;
|
||||
output_buffer->u.YUVA.v_size = uv_stride * (h + 1) / 2;
|
||||
tmp += output_buffer->u.YUVA.v_size;
|
||||
assert(tmp <= external_buffer + total_size);
|
||||
}
|
||||
output_buffer->is_external_memory = use_external_memory;
|
||||
return external_buffer;
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
int ok = 0;
|
||||
const char* in_file = NULL;
|
||||
const char* out_file = NULL;
|
||||
|
||||
WebPDecoderConfig config;
|
||||
WebPDecBuffer* const output_buffer = &config.output;
|
||||
WebPBitstreamFeatures* const bitstream = &config.input;
|
||||
WebPOutputFileFormat format = PNG;
|
||||
uint8_t* external_buffer = NULL;
|
||||
int use_external_memory = 0;
|
||||
const uint8_t* data = NULL;
|
||||
|
||||
int incremental = 0;
|
||||
int c;
|
||||
|
||||
INIT_WARGV(argc, argv);
|
||||
|
||||
if (!WebPInitDecoderConfig(&config)) {
|
||||
fprintf(stderr, "Library version mismatch!\n");
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
|
||||
for (c = 1; c < argc; ++c) {
|
||||
int parse_error = 0;
|
||||
if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else if (!strcmp(argv[c], "-o") && c < argc - 1) {
|
||||
out_file = (const char*)GET_WARGV(argv, ++c);
|
||||
} else if (!strcmp(argv[c], "-alpha")) {
|
||||
format = ALPHA_PLANE_ONLY;
|
||||
} else if (!strcmp(argv[c], "-nofancy")) {
|
||||
config.options.no_fancy_upsampling = 1;
|
||||
} else if (!strcmp(argv[c], "-nofilter")) {
|
||||
config.options.bypass_filtering = 1;
|
||||
} else if (!strcmp(argv[c], "-pam")) {
|
||||
format = PAM;
|
||||
} else if (!strcmp(argv[c], "-ppm")) {
|
||||
format = PPM;
|
||||
} else if (!strcmp(argv[c], "-bmp")) {
|
||||
format = BMP;
|
||||
} else if (!strcmp(argv[c], "-tiff")) {
|
||||
format = TIFF;
|
||||
} else if (!strcmp(argv[c], "-quiet")) {
|
||||
quiet = 1;
|
||||
} else if (!strcmp(argv[c], "-version")) {
|
||||
const int version = WebPGetDecoderVersion();
|
||||
printf("%d.%d.%d\n",
|
||||
(version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else if (!strcmp(argv[c], "-pgm")) {
|
||||
format = PGM;
|
||||
} else if (!strcmp(argv[c], "-yuv")) {
|
||||
format = RAW_YUV;
|
||||
} else if (!strcmp(argv[c], "-pixel_format") && c < argc - 1) {
|
||||
const char* const fmt = argv[++c];
|
||||
if (!strcmp(fmt, "RGB")) format = RGB;
|
||||
else if (!strcmp(fmt, "RGBA")) format = RGBA;
|
||||
else if (!strcmp(fmt, "BGR")) format = BGR;
|
||||
else if (!strcmp(fmt, "BGRA")) format = BGRA;
|
||||
else if (!strcmp(fmt, "ARGB")) format = ARGB;
|
||||
else if (!strcmp(fmt, "RGBA_4444")) format = RGBA_4444;
|
||||
else if (!strcmp(fmt, "RGB_565")) format = RGB_565;
|
||||
else if (!strcmp(fmt, "rgbA")) format = rgbA;
|
||||
else if (!strcmp(fmt, "bgrA")) format = bgrA;
|
||||
else if (!strcmp(fmt, "Argb")) format = Argb;
|
||||
else if (!strcmp(fmt, "rgbA_4444")) format = rgbA_4444;
|
||||
else if (!strcmp(fmt, "YUV")) format = YUV;
|
||||
else if (!strcmp(fmt, "YUVA")) format = YUVA;
|
||||
else {
|
||||
fprintf(stderr, "Can't parse pixel_format %s\n", fmt);
|
||||
parse_error = 1;
|
||||
}
|
||||
} else if (!strcmp(argv[c], "-external_memory") && c < argc - 1) {
|
||||
use_external_memory = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
parse_error |= (use_external_memory > 2 || use_external_memory < 0);
|
||||
if (parse_error) {
|
||||
fprintf(stderr, "Can't parse 'external_memory' value %s\n", argv[c]);
|
||||
}
|
||||
} else if (!strcmp(argv[c], "-mt")) {
|
||||
config.options.use_threads = 1;
|
||||
} else if (!strcmp(argv[c], "-alpha_dither")) {
|
||||
config.options.alpha_dithering_strength = 100;
|
||||
} else if (!strcmp(argv[c], "-nodither")) {
|
||||
config.options.dithering_strength = 0;
|
||||
} else if (!strcmp(argv[c], "-dither") && c < argc - 1) {
|
||||
config.options.dithering_strength =
|
||||
ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else if (!strcmp(argv[c], "-crop") && c < argc - 4) {
|
||||
config.options.use_cropping = 1;
|
||||
config.options.crop_left = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
config.options.crop_top = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
config.options.crop_width = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
config.options.crop_height = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else if ((!strcmp(argv[c], "-scale") || !strcmp(argv[c], "-resize")) &&
|
||||
c < argc - 2) { // '-scale' is left for compatibility
|
||||
config.options.use_scaling = 1;
|
||||
config.options.scaled_width = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
config.options.scaled_height = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else if (!strcmp(argv[c], "-flip")) {
|
||||
config.options.flip = 1;
|
||||
} else if (!strcmp(argv[c], "-v")) {
|
||||
verbose = 1;
|
||||
#ifndef WEBP_DLL
|
||||
} else if (!strcmp(argv[c], "-noasm")) {
|
||||
VP8GetCPUInfo = NULL;
|
||||
#endif
|
||||
} else if (!strcmp(argv[c], "-incremental")) {
|
||||
incremental = 1;
|
||||
} else if (!strcmp(argv[c], "--")) {
|
||||
if (c < argc - 1) in_file = (const char*)GET_WARGV(argv, ++c);
|
||||
break;
|
||||
} else if (argv[c][0] == '-') {
|
||||
fprintf(stderr, "Unknown option '%s'\n", argv[c]);
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
} else {
|
||||
in_file = (const char*)GET_WARGV(argv, c);
|
||||
}
|
||||
|
||||
if (parse_error) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if (in_file == NULL) {
|
||||
fprintf(stderr, "missing input file!!\n");
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
|
||||
if (quiet) verbose = 0;
|
||||
|
||||
{
|
||||
VP8StatusCode status = VP8_STATUS_OK;
|
||||
size_t data_size = 0;
|
||||
if (!LoadWebP(in_file, &data, &data_size, bitstream)) {
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case PNG:
|
||||
#ifdef HAVE_WINCODEC_H
|
||||
output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR;
|
||||
#else
|
||||
output_buffer->colorspace = bitstream->has_alpha ? MODE_RGBA : MODE_RGB;
|
||||
#endif
|
||||
break;
|
||||
case PAM:
|
||||
output_buffer->colorspace = MODE_RGBA;
|
||||
break;
|
||||
case PPM:
|
||||
output_buffer->colorspace = MODE_RGB; // drops alpha for PPM
|
||||
break;
|
||||
case BMP:
|
||||
output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR;
|
||||
break;
|
||||
case TIFF:
|
||||
output_buffer->colorspace = bitstream->has_alpha ? MODE_RGBA : MODE_RGB;
|
||||
break;
|
||||
case PGM:
|
||||
case RAW_YUV:
|
||||
output_buffer->colorspace = bitstream->has_alpha ? MODE_YUVA : MODE_YUV;
|
||||
break;
|
||||
case ALPHA_PLANE_ONLY:
|
||||
output_buffer->colorspace = MODE_YUVA;
|
||||
break;
|
||||
// forced modes:
|
||||
case RGB: output_buffer->colorspace = MODE_RGB; break;
|
||||
case RGBA: output_buffer->colorspace = MODE_RGBA; break;
|
||||
case BGR: output_buffer->colorspace = MODE_BGR; break;
|
||||
case BGRA: output_buffer->colorspace = MODE_BGRA; break;
|
||||
case ARGB: output_buffer->colorspace = MODE_ARGB; break;
|
||||
case RGBA_4444: output_buffer->colorspace = MODE_RGBA_4444; break;
|
||||
case RGB_565: output_buffer->colorspace = MODE_RGB_565; break;
|
||||
case rgbA: output_buffer->colorspace = MODE_rgbA; break;
|
||||
case bgrA: output_buffer->colorspace = MODE_bgrA; break;
|
||||
case Argb: output_buffer->colorspace = MODE_Argb; break;
|
||||
case rgbA_4444: output_buffer->colorspace = MODE_rgbA_4444; break;
|
||||
case YUV: output_buffer->colorspace = MODE_YUV; break;
|
||||
case YUVA: output_buffer->colorspace = MODE_YUVA; break;
|
||||
default: goto Exit;
|
||||
}
|
||||
|
||||
if (use_external_memory > 0 && format >= RGB) {
|
||||
external_buffer = AllocateExternalBuffer(&config, format,
|
||||
use_external_memory);
|
||||
if (external_buffer == NULL) goto Exit;
|
||||
}
|
||||
|
||||
{
|
||||
Stopwatch stop_watch;
|
||||
if (verbose) StopwatchReset(&stop_watch);
|
||||
|
||||
if (incremental) {
|
||||
status = DecodeWebPIncremental(data, data_size, &config);
|
||||
} else {
|
||||
status = DecodeWebP(data, data_size, &config);
|
||||
}
|
||||
if (verbose) {
|
||||
const double decode_time = StopwatchReadAndReset(&stop_watch);
|
||||
fprintf(stderr, "Time to decode picture: %.3fs\n", decode_time);
|
||||
}
|
||||
}
|
||||
|
||||
ok = (status == VP8_STATUS_OK);
|
||||
if (!ok) {
|
||||
PrintWebPError(in_file, status);
|
||||
goto Exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (out_file != NULL) {
|
||||
if (!quiet) {
|
||||
WFPRINTF(stderr, "Decoded %s.", (const W_CHAR*)in_file);
|
||||
fprintf(stderr, " Dimensions: %d x %d %s. Format: %s. Now saving...\n",
|
||||
output_buffer->width, output_buffer->height,
|
||||
bitstream->has_alpha ? " (with alpha)" : "",
|
||||
kFormatType[bitstream->format]);
|
||||
}
|
||||
ok = SaveOutput(output_buffer, format, out_file);
|
||||
} else {
|
||||
if (!quiet) {
|
||||
WFPRINTF(stderr, "File %s can be decoded ", (const W_CHAR*)in_file);
|
||||
fprintf(stderr, "(dimensions: %d x %d %s. Format: %s).\n",
|
||||
output_buffer->width, output_buffer->height,
|
||||
bitstream->has_alpha ? " (with alpha)" : "",
|
||||
kFormatType[bitstream->format]);
|
||||
fprintf(stderr, "Nothing written; "
|
||||
"use -o flag to save the result as e.g. PNG.\n");
|
||||
}
|
||||
}
|
||||
Exit:
|
||||
WebPFreeDecBuffer(output_buffer);
|
||||
WebPFree((void*)external_buffer);
|
||||
WebPFree((void*)data);
|
||||
FREE_WARGV_AND_RETURN(ok ? 0 : -1);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
139
examples/example_util.c
Normal file
139
examples/example_util.c
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2012 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Utility functions used by the example programs.
|
||||
//
|
||||
|
||||
#include "./example_util.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "webp/mux_types.h"
|
||||
#include "../imageio/imageio_util.h"
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// String parsing
|
||||
|
||||
uint32_t ExUtilGetUInt(const char* const v, int base, int* const error) {
|
||||
char* end = NULL;
|
||||
const uint32_t n = (v != NULL) ? (uint32_t)strtoul(v, &end, base) : 0u;
|
||||
if (end == v && error != NULL && !*error) {
|
||||
*error = 1;
|
||||
fprintf(stderr, "Error! '%s' is not an integer.\n",
|
||||
(v != NULL) ? v : "(null)");
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
int ExUtilGetInt(const char* const v, int base, int* const error) {
|
||||
return (int)ExUtilGetUInt(v, base, error);
|
||||
}
|
||||
|
||||
int ExUtilGetInts(const char* v, int base, int max_output, int output[]) {
|
||||
int n, error = 0;
|
||||
for (n = 0; v != NULL && n < max_output; ++n) {
|
||||
const int value = ExUtilGetInt(v, base, &error);
|
||||
if (error) return -1;
|
||||
output[n] = value;
|
||||
v = strchr(v, ',');
|
||||
if (v != NULL) ++v; // skip over the trailing ','
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
float ExUtilGetFloat(const char* const v, int* const error) {
|
||||
char* end = NULL;
|
||||
const float f = (v != NULL) ? (float)strtod(v, &end) : 0.f;
|
||||
if (end == v && error != NULL && !*error) {
|
||||
*error = 1;
|
||||
fprintf(stderr, "Error! '%s' is not a floating point number.\n",
|
||||
(v != NULL) ? v : "(null)");
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static void ResetCommandLineArguments(int argc, const char* argv[],
|
||||
CommandLineArguments* const args) {
|
||||
assert(args != NULL);
|
||||
args->argc_ = argc;
|
||||
args->argv_ = argv;
|
||||
args->own_argv_ = 0;
|
||||
WebPDataInit(&args->argv_data_);
|
||||
}
|
||||
|
||||
void ExUtilDeleteCommandLineArguments(CommandLineArguments* const args) {
|
||||
if (args != NULL) {
|
||||
if (args->own_argv_) {
|
||||
WebPFree((void*)args->argv_);
|
||||
WebPDataClear(&args->argv_data_);
|
||||
}
|
||||
ResetCommandLineArguments(0, NULL, args);
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_ARGC 16384
|
||||
int ExUtilInitCommandLineArguments(int argc, const char* argv[],
|
||||
CommandLineArguments* const args) {
|
||||
if (args == NULL || argv == NULL) return 0;
|
||||
ResetCommandLineArguments(argc, argv, args);
|
||||
if (argc == 1 && argv[0][0] != '-') {
|
||||
char* cur;
|
||||
const char sep[] = " \t\r\n\f\v";
|
||||
|
||||
#if defined(_WIN32) && defined(_UNICODE)
|
||||
fprintf(stderr,
|
||||
"Error: Reading arguments from a file is a feature unavailable "
|
||||
"with Unicode binaries.\n");
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
if (!ExUtilReadFileToWebPData(argv[0], &args->argv_data_)) {
|
||||
return 0;
|
||||
}
|
||||
args->own_argv_ = 1;
|
||||
args->argv_ = (const char**)WebPMalloc(MAX_ARGC * sizeof(*args->argv_));
|
||||
if (args->argv_ == NULL) {
|
||||
ExUtilDeleteCommandLineArguments(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
argc = 0;
|
||||
for (cur = strtok((char*)args->argv_data_.bytes, sep);
|
||||
cur != NULL;
|
||||
cur = strtok(NULL, sep)) {
|
||||
if (argc == MAX_ARGC) {
|
||||
fprintf(stderr, "ERROR: Arguments limit %d reached\n", MAX_ARGC);
|
||||
ExUtilDeleteCommandLineArguments(args);
|
||||
return 0;
|
||||
}
|
||||
assert(strlen(cur) != 0);
|
||||
args->argv_[argc++] = cur;
|
||||
}
|
||||
args->argc_ = argc;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int ExUtilReadFileToWebPData(const char* const filename,
|
||||
WebPData* const webp_data) {
|
||||
const uint8_t* data;
|
||||
size_t size;
|
||||
if (webp_data == NULL) return 0;
|
||||
if (!ImgIoUtilReadFile(filename, &data, &size)) return 0;
|
||||
webp_data->bytes = data;
|
||||
webp_data->size = size;
|
||||
return 1;
|
||||
}
|
70
examples/example_util.h
Normal file
70
examples/example_util.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2012 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Utility functions used by the example programs.
|
||||
//
|
||||
|
||||
#ifndef WEBP_EXAMPLES_EXAMPLE_UTIL_H_
|
||||
#define WEBP_EXAMPLES_EXAMPLE_UTIL_H_
|
||||
|
||||
#include "webp/types.h"
|
||||
#include "webp/mux_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// String parsing
|
||||
|
||||
// Parses 'v' using strto(ul|l|d)(). If error is non-NULL, '*error' is set to
|
||||
// true on failure while on success it is left unmodified to allow chaining of
|
||||
// calls. An error is only printed on the first occurrence.
|
||||
uint32_t ExUtilGetUInt(const char* const v, int base, int* const error);
|
||||
int ExUtilGetInt(const char* const v, int base, int* const error);
|
||||
float ExUtilGetFloat(const char* const v, int* const error);
|
||||
|
||||
// This variant of ExUtilGetInt() will parse multiple integers from a
|
||||
// comma-separated list. Up to 'max_output' integers are parsed.
|
||||
// The result is placed in the output[] array, and the number of integers
|
||||
// actually parsed is returned, or -1 if an error occurred.
|
||||
int ExUtilGetInts(const char* v, int base, int max_output, int output[]);
|
||||
|
||||
// Reads a file named 'filename' into a WebPData structure. The content of
|
||||
// webp_data is overwritten. Returns false in case of error.
|
||||
int ExUtilReadFileToWebPData(const char* const filename,
|
||||
WebPData* const webp_data);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Command-line arguments
|
||||
|
||||
typedef struct {
|
||||
int argc_;
|
||||
const char** argv_;
|
||||
WebPData argv_data_;
|
||||
int own_argv_;
|
||||
} CommandLineArguments;
|
||||
|
||||
// Initializes the structure from the command-line parameters. If there is
|
||||
// only one parameter and it does not start with a '-', then it is assumed to
|
||||
// be a file name. This file will be read and tokenized into command-line
|
||||
// arguments. The content of 'args' is overwritten.
|
||||
// Returns false in case of error (memory allocation failure, non
|
||||
// existing file, too many arguments, ...).
|
||||
int ExUtilInitCommandLineArguments(int argc, const char* argv[],
|
||||
CommandLineArguments* const args);
|
||||
|
||||
// Deallocate all memory and reset 'args'.
|
||||
void ExUtilDeleteCommandLineArguments(CommandLineArguments* const args);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // WEBP_EXAMPLES_EXAMPLE_UTIL_H_
|
609
examples/gif2webp.c
Normal file
609
examples/gif2webp.c
Normal file
@@ -0,0 +1,609 @@
|
||||
// Copyright 2012 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// simple tool to convert animated GIFs to WebP
|
||||
//
|
||||
// Authors: Skal (pascal.massimino@gmail.com)
|
||||
// Urvang (urvang@google.com)
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "webp/config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WEBP_HAVE_GIF
|
||||
|
||||
#if defined(HAVE_UNISTD_H) && HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <gif_lib.h>
|
||||
#include "webp/encode.h"
|
||||
#include "webp/mux.h"
|
||||
#include "../examples/example_util.h"
|
||||
#include "../imageio/imageio_util.h"
|
||||
#include "./gifdec.h"
|
||||
#include "./unicode.h"
|
||||
#include "./unicode_gif.h"
|
||||
|
||||
#if !defined(STDIN_FILENO)
|
||||
#define STDIN_FILENO 0
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static int transparent_index = GIF_INDEX_INVALID; // Opaque by default.
|
||||
|
||||
static const char* const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = {
|
||||
"WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA",
|
||||
"WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA"
|
||||
};
|
||||
|
||||
static const char* ErrorString(WebPMuxError err) {
|
||||
assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
|
||||
return kErrorMessages[-err];
|
||||
}
|
||||
|
||||
enum {
|
||||
METADATA_ICC = (1 << 0),
|
||||
METADATA_XMP = (1 << 1),
|
||||
METADATA_ALL = METADATA_ICC | METADATA_XMP
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static void Help(void) {
|
||||
printf("Usage:\n");
|
||||
printf(" gif2webp [options] gif_file -o webp_file\n");
|
||||
printf("Options:\n");
|
||||
printf(" -h / -help ............. this help\n");
|
||||
printf(" -lossy ................. encode image using lossy compression\n");
|
||||
printf(" -mixed ................. for each frame in the image, pick lossy\n"
|
||||
" or lossless compression heuristically\n");
|
||||
printf(" -q <float> ............. quality factor (0:small..100:big)\n");
|
||||
printf(" -m <int> ............... compression method (0=fast, 6=slowest)\n");
|
||||
printf(" -min_size .............. minimize output size (default:off)\n"
|
||||
" lossless compression by default; can be\n"
|
||||
" combined with -q, -m, -lossy or -mixed\n"
|
||||
" options\n");
|
||||
printf(" -kmin <int> ............ min distance between key frames\n");
|
||||
printf(" -kmax <int> ............ max distance between key frames\n");
|
||||
printf(" -f <int> ............... filter strength (0=off..100)\n");
|
||||
printf(" -metadata <string> ..... comma separated list of metadata to\n");
|
||||
printf(" ");
|
||||
printf("copy from the input to the output if present\n");
|
||||
printf(" ");
|
||||
printf("Valid values: all, none, icc, xmp (default)\n");
|
||||
printf(" -loop_compatibility .... use compatibility mode for Chrome\n");
|
||||
printf(" version prior to M62 (inclusive)\n");
|
||||
printf(" -mt .................... use multi-threading if available\n");
|
||||
printf("\n");
|
||||
printf(" -version ............... print version number and exit\n");
|
||||
printf(" -v ..................... verbose\n");
|
||||
printf(" -quiet ................. don't print anything\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
int verbose = 0;
|
||||
int gif_error = GIF_ERROR;
|
||||
WebPMuxError err = WEBP_MUX_OK;
|
||||
int ok = 0;
|
||||
const W_CHAR* in_file = NULL, *out_file = NULL;
|
||||
GifFileType* gif = NULL;
|
||||
int frame_duration = 0;
|
||||
int frame_timestamp = 0;
|
||||
GIFDisposeMethod orig_dispose = GIF_DISPOSE_NONE;
|
||||
|
||||
WebPPicture frame; // Frame rectangle only (not disposed).
|
||||
WebPPicture curr_canvas; // Not disposed.
|
||||
WebPPicture prev_canvas; // Disposed.
|
||||
|
||||
WebPAnimEncoder* enc = NULL;
|
||||
WebPAnimEncoderOptions enc_options;
|
||||
WebPConfig config;
|
||||
|
||||
int frame_number = 0; // Whether we are processing the first frame.
|
||||
int done;
|
||||
int c;
|
||||
int quiet = 0;
|
||||
WebPData webp_data;
|
||||
|
||||
int keep_metadata = METADATA_XMP; // ICC not output by default.
|
||||
WebPData icc_data;
|
||||
int stored_icc = 0; // Whether we have already stored an ICC profile.
|
||||
WebPData xmp_data;
|
||||
int stored_xmp = 0; // Whether we have already stored an XMP profile.
|
||||
int loop_count = 0; // default: infinite
|
||||
int stored_loop_count = 0; // Whether we have found an explicit loop count.
|
||||
int loop_compatibility = 0;
|
||||
WebPMux* mux = NULL;
|
||||
|
||||
int default_kmin = 1; // Whether to use default kmin value.
|
||||
int default_kmax = 1;
|
||||
|
||||
INIT_WARGV(argc, argv);
|
||||
|
||||
if (!WebPConfigInit(&config) || !WebPAnimEncoderOptionsInit(&enc_options) ||
|
||||
!WebPPictureInit(&frame) || !WebPPictureInit(&curr_canvas) ||
|
||||
!WebPPictureInit(&prev_canvas)) {
|
||||
fprintf(stderr, "Error! Version mismatch!\n");
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
config.lossless = 1; // Use lossless compression by default.
|
||||
|
||||
WebPDataInit(&webp_data);
|
||||
WebPDataInit(&icc_data);
|
||||
WebPDataInit(&xmp_data);
|
||||
|
||||
if (argc == 1) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
}
|
||||
|
||||
for (c = 1; c < argc; ++c) {
|
||||
int parse_error = 0;
|
||||
if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else if (!strcmp(argv[c], "-o") && c < argc - 1) {
|
||||
out_file = GET_WARGV(argv, ++c);
|
||||
} else if (!strcmp(argv[c], "-lossy")) {
|
||||
config.lossless = 0;
|
||||
} else if (!strcmp(argv[c], "-mixed")) {
|
||||
enc_options.allow_mixed = 1;
|
||||
config.lossless = 0;
|
||||
} else if (!strcmp(argv[c], "-loop_compatibility")) {
|
||||
loop_compatibility = 1;
|
||||
} else if (!strcmp(argv[c], "-q") && c < argc - 1) {
|
||||
config.quality = ExUtilGetFloat(argv[++c], &parse_error);
|
||||
} else if (!strcmp(argv[c], "-m") && c < argc - 1) {
|
||||
config.method = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else if (!strcmp(argv[c], "-min_size")) {
|
||||
enc_options.minimize_size = 1;
|
||||
} else if (!strcmp(argv[c], "-kmax") && c < argc - 1) {
|
||||
enc_options.kmax = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
default_kmax = 0;
|
||||
} else if (!strcmp(argv[c], "-kmin") && c < argc - 1) {
|
||||
enc_options.kmin = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
default_kmin = 0;
|
||||
} else if (!strcmp(argv[c], "-f") && c < argc - 1) {
|
||||
config.filter_strength = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else if (!strcmp(argv[c], "-metadata") && c < argc - 1) {
|
||||
static const struct {
|
||||
const char* option;
|
||||
int flag;
|
||||
} kTokens[] = {
|
||||
{ "all", METADATA_ALL },
|
||||
{ "none", 0 },
|
||||
{ "icc", METADATA_ICC },
|
||||
{ "xmp", METADATA_XMP },
|
||||
};
|
||||
const size_t kNumTokens = sizeof(kTokens) / sizeof(*kTokens);
|
||||
const char* start = argv[++c];
|
||||
const char* const end = start + strlen(start);
|
||||
|
||||
keep_metadata = 0;
|
||||
while (start < end) {
|
||||
size_t i;
|
||||
const char* token = strchr(start, ',');
|
||||
if (token == NULL) token = end;
|
||||
|
||||
for (i = 0; i < kNumTokens; ++i) {
|
||||
if ((size_t)(token - start) == strlen(kTokens[i].option) &&
|
||||
!strncmp(start, kTokens[i].option, strlen(kTokens[i].option))) {
|
||||
if (kTokens[i].flag != 0) {
|
||||
keep_metadata |= kTokens[i].flag;
|
||||
} else {
|
||||
keep_metadata = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == kNumTokens) {
|
||||
fprintf(stderr, "Error! Unknown metadata type '%.*s'\n",
|
||||
(int)(token - start), start);
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
start = token + 1;
|
||||
}
|
||||
} else if (!strcmp(argv[c], "-mt")) {
|
||||
++config.thread_level;
|
||||
} else if (!strcmp(argv[c], "-version")) {
|
||||
const int enc_version = WebPGetEncoderVersion();
|
||||
const int mux_version = WebPGetMuxVersion();
|
||||
printf("WebP Encoder version: %d.%d.%d\nWebP Mux version: %d.%d.%d\n",
|
||||
(enc_version >> 16) & 0xff, (enc_version >> 8) & 0xff,
|
||||
enc_version & 0xff, (mux_version >> 16) & 0xff,
|
||||
(mux_version >> 8) & 0xff, mux_version & 0xff);
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else if (!strcmp(argv[c], "-quiet")) {
|
||||
quiet = 1;
|
||||
enc_options.verbose = 0;
|
||||
} else if (!strcmp(argv[c], "-v")) {
|
||||
verbose = 1;
|
||||
enc_options.verbose = 1;
|
||||
} else if (!strcmp(argv[c], "--")) {
|
||||
if (c < argc - 1) in_file = GET_WARGV(argv, ++c);
|
||||
break;
|
||||
} else if (argv[c][0] == '-') {
|
||||
fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]);
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
} else {
|
||||
in_file = GET_WARGV(argv, c);
|
||||
}
|
||||
|
||||
if (parse_error) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// Appropriate default kmin, kmax values for lossy and lossless.
|
||||
if (default_kmin) {
|
||||
enc_options.kmin = config.lossless ? 9 : 3;
|
||||
}
|
||||
if (default_kmax) {
|
||||
enc_options.kmax = config.lossless ? 17 : 5;
|
||||
}
|
||||
|
||||
if (!WebPValidateConfig(&config)) {
|
||||
fprintf(stderr, "Error! Invalid configuration.\n");
|
||||
goto End;
|
||||
}
|
||||
|
||||
if (in_file == NULL) {
|
||||
fprintf(stderr, "No input file specified!\n");
|
||||
Help();
|
||||
goto End;
|
||||
}
|
||||
|
||||
// Start the decoder object
|
||||
gif = DGifOpenFileUnicode(in_file, &gif_error);
|
||||
if (gif == NULL) goto End;
|
||||
|
||||
// Loop over GIF images
|
||||
done = 0;
|
||||
do {
|
||||
GifRecordType type;
|
||||
if (DGifGetRecordType(gif, &type) == GIF_ERROR) goto End;
|
||||
|
||||
switch (type) {
|
||||
case IMAGE_DESC_RECORD_TYPE: {
|
||||
GIFFrameRect gif_rect;
|
||||
GifImageDesc* const image_desc = &gif->Image;
|
||||
|
||||
if (!DGifGetImageDesc(gif)) goto End;
|
||||
|
||||
if (frame_number == 0) {
|
||||
if (verbose) {
|
||||
printf("Canvas screen: %d x %d\n", gif->SWidth, gif->SHeight);
|
||||
}
|
||||
// Fix some broken GIF global headers that report
|
||||
// 0 x 0 screen dimension.
|
||||
if (gif->SWidth == 0 || gif->SHeight == 0) {
|
||||
image_desc->Left = 0;
|
||||
image_desc->Top = 0;
|
||||
gif->SWidth = image_desc->Width;
|
||||
gif->SHeight = image_desc->Height;
|
||||
if (gif->SWidth <= 0 || gif->SHeight <= 0) {
|
||||
goto End;
|
||||
}
|
||||
if (verbose) {
|
||||
printf("Fixed canvas screen dimension to: %d x %d\n",
|
||||
gif->SWidth, gif->SHeight);
|
||||
}
|
||||
}
|
||||
// Allocate current buffer.
|
||||
frame.width = gif->SWidth;
|
||||
frame.height = gif->SHeight;
|
||||
frame.use_argb = 1;
|
||||
if (!WebPPictureAlloc(&frame)) goto End;
|
||||
GIFClearPic(&frame, NULL);
|
||||
if (!(WebPPictureCopy(&frame, &curr_canvas) &&
|
||||
WebPPictureCopy(&frame, &prev_canvas))) {
|
||||
fprintf(stderr, "Error allocating canvas.\n");
|
||||
goto End;
|
||||
}
|
||||
|
||||
// Background color.
|
||||
GIFGetBackgroundColor(gif->SColorMap, gif->SBackGroundColor,
|
||||
transparent_index,
|
||||
&enc_options.anim_params.bgcolor);
|
||||
|
||||
// Initialize encoder.
|
||||
enc = WebPAnimEncoderNew(curr_canvas.width, curr_canvas.height,
|
||||
&enc_options);
|
||||
if (enc == NULL) {
|
||||
fprintf(stderr,
|
||||
"Error! Could not create encoder object. Possibly due to "
|
||||
"a memory error.\n");
|
||||
goto End;
|
||||
}
|
||||
}
|
||||
|
||||
// Some even more broken GIF can have sub-rect with zero width/height.
|
||||
if (image_desc->Width == 0 || image_desc->Height == 0) {
|
||||
image_desc->Width = gif->SWidth;
|
||||
image_desc->Height = gif->SHeight;
|
||||
}
|
||||
|
||||
if (!GIFReadFrame(gif, transparent_index, &gif_rect, &frame)) {
|
||||
goto End;
|
||||
}
|
||||
// Blend frame rectangle with previous canvas to compose full canvas.
|
||||
// Note that 'curr_canvas' is same as 'prev_canvas' at this point.
|
||||
GIFBlendFrames(&frame, &gif_rect, &curr_canvas);
|
||||
|
||||
if (!WebPAnimEncoderAdd(enc, &curr_canvas, frame_timestamp, &config)) {
|
||||
fprintf(stderr, "Error while adding frame #%d: %s\n", frame_number,
|
||||
WebPAnimEncoderGetError(enc));
|
||||
goto End;
|
||||
} else {
|
||||
++frame_number;
|
||||
}
|
||||
|
||||
// Update canvases.
|
||||
GIFDisposeFrame(orig_dispose, &gif_rect, &prev_canvas, &curr_canvas);
|
||||
GIFCopyPixels(&curr_canvas, &prev_canvas);
|
||||
|
||||
// Force frames with a small or no duration to 100ms to be consistent
|
||||
// with web browsers and other transcoding tools. This also avoids
|
||||
// incorrect durations between frames when padding frames are
|
||||
// discarded.
|
||||
if (frame_duration <= 10) {
|
||||
frame_duration = 100;
|
||||
}
|
||||
|
||||
// Update timestamp (for next frame).
|
||||
frame_timestamp += frame_duration;
|
||||
|
||||
// In GIF, graphic control extensions are optional for a frame, so we
|
||||
// may not get one before reading the next frame. To handle this case,
|
||||
// we reset frame properties to reasonable defaults for the next frame.
|
||||
orig_dispose = GIF_DISPOSE_NONE;
|
||||
frame_duration = 0;
|
||||
transparent_index = GIF_INDEX_INVALID;
|
||||
break;
|
||||
}
|
||||
case EXTENSION_RECORD_TYPE: {
|
||||
int extension;
|
||||
GifByteType* data = NULL;
|
||||
if (DGifGetExtension(gif, &extension, &data) == GIF_ERROR) {
|
||||
goto End;
|
||||
}
|
||||
if (data == NULL) continue;
|
||||
|
||||
switch (extension) {
|
||||
case COMMENT_EXT_FUNC_CODE: {
|
||||
break; // Do nothing for now.
|
||||
}
|
||||
case GRAPHICS_EXT_FUNC_CODE: {
|
||||
if (!GIFReadGraphicsExtension(data, &frame_duration, &orig_dispose,
|
||||
&transparent_index)) {
|
||||
goto End;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PLAINTEXT_EXT_FUNC_CODE: {
|
||||
break;
|
||||
}
|
||||
case APPLICATION_EXT_FUNC_CODE: {
|
||||
if (data[0] != 11) break; // Chunk is too short
|
||||
if (!memcmp(data + 1, "NETSCAPE2.0", 11) ||
|
||||
!memcmp(data + 1, "ANIMEXTS1.0", 11)) {
|
||||
if (!GIFReadLoopCount(gif, &data, &loop_count)) {
|
||||
goto End;
|
||||
}
|
||||
if (verbose) {
|
||||
fprintf(stderr, "Loop count: %d\n", loop_count);
|
||||
}
|
||||
stored_loop_count = loop_compatibility ? (loop_count != 0) : 1;
|
||||
} else { // An extension containing metadata.
|
||||
// We only store the first encountered chunk of each type, and
|
||||
// only if requested by the user.
|
||||
const int is_xmp = (keep_metadata & METADATA_XMP) &&
|
||||
!stored_xmp &&
|
||||
!memcmp(data + 1, "XMP DataXMP", 11);
|
||||
const int is_icc = (keep_metadata & METADATA_ICC) &&
|
||||
!stored_icc &&
|
||||
!memcmp(data + 1, "ICCRGBG1012", 11);
|
||||
if (is_xmp || is_icc) {
|
||||
if (!GIFReadMetadata(gif, &data,
|
||||
is_xmp ? &xmp_data : &icc_data)) {
|
||||
goto End;
|
||||
}
|
||||
if (is_icc) {
|
||||
stored_icc = 1;
|
||||
} else if (is_xmp) {
|
||||
stored_xmp = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break; // skip
|
||||
}
|
||||
}
|
||||
while (data != NULL) {
|
||||
if (DGifGetExtensionNext(gif, &data) == GIF_ERROR) goto End;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TERMINATE_RECORD_TYPE: {
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (verbose) {
|
||||
fprintf(stderr, "Skipping over unknown record type %d\n", type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!done);
|
||||
|
||||
// Last NULL frame.
|
||||
if (!WebPAnimEncoderAdd(enc, NULL, frame_timestamp, NULL)) {
|
||||
fprintf(stderr, "Error flushing WebP muxer.\n");
|
||||
fprintf(stderr, "%s\n", WebPAnimEncoderGetError(enc));
|
||||
}
|
||||
|
||||
if (!WebPAnimEncoderAssemble(enc, &webp_data)) {
|
||||
fprintf(stderr, "%s\n", WebPAnimEncoderGetError(enc));
|
||||
goto End;
|
||||
}
|
||||
// If there's only one frame, we don't need to handle loop count.
|
||||
if (frame_number == 1) {
|
||||
loop_count = 0;
|
||||
} else if (!loop_compatibility) {
|
||||
if (!stored_loop_count) {
|
||||
// if no loop-count element is seen, the default is '1' (loop-once)
|
||||
// and we need to signal it explicitly in WebP. Note however that
|
||||
// in case there's a single frame, we still don't need to store it.
|
||||
if (frame_number > 1) {
|
||||
stored_loop_count = 1;
|
||||
loop_count = 1;
|
||||
}
|
||||
} else if (loop_count > 0 && loop_count < 65535) {
|
||||
// adapt GIF's semantic to WebP's (except in the infinite-loop case)
|
||||
loop_count += 1;
|
||||
}
|
||||
}
|
||||
// loop_count of 0 is the default (infinite), so no need to signal it
|
||||
if (loop_count == 0) stored_loop_count = 0;
|
||||
|
||||
if (stored_loop_count || stored_icc || stored_xmp) {
|
||||
// Re-mux to add loop count and/or metadata as needed.
|
||||
mux = WebPMuxCreate(&webp_data, 1);
|
||||
if (mux == NULL) {
|
||||
fprintf(stderr, "ERROR: Could not re-mux to add loop count/metadata.\n");
|
||||
goto End;
|
||||
}
|
||||
WebPDataClear(&webp_data);
|
||||
|
||||
if (stored_loop_count) { // Update loop count.
|
||||
WebPMuxAnimParams new_params;
|
||||
err = WebPMuxGetAnimationParams(mux, &new_params);
|
||||
if (err != WEBP_MUX_OK) {
|
||||
fprintf(stderr, "ERROR (%s): Could not fetch loop count.\n",
|
||||
ErrorString(err));
|
||||
goto End;
|
||||
}
|
||||
new_params.loop_count = loop_count;
|
||||
err = WebPMuxSetAnimationParams(mux, &new_params);
|
||||
if (err != WEBP_MUX_OK) {
|
||||
fprintf(stderr, "ERROR (%s): Could not update loop count.\n",
|
||||
ErrorString(err));
|
||||
goto End;
|
||||
}
|
||||
}
|
||||
|
||||
if (stored_icc) { // Add ICCP chunk.
|
||||
err = WebPMuxSetChunk(mux, "ICCP", &icc_data, 1);
|
||||
if (verbose) {
|
||||
fprintf(stderr, "ICC size: %d\n", (int)icc_data.size);
|
||||
}
|
||||
if (err != WEBP_MUX_OK) {
|
||||
fprintf(stderr, "ERROR (%s): Could not set ICC chunk.\n",
|
||||
ErrorString(err));
|
||||
goto End;
|
||||
}
|
||||
}
|
||||
|
||||
if (stored_xmp) { // Add XMP chunk.
|
||||
err = WebPMuxSetChunk(mux, "XMP ", &xmp_data, 1);
|
||||
if (verbose) {
|
||||
fprintf(stderr, "XMP size: %d\n", (int)xmp_data.size);
|
||||
}
|
||||
if (err != WEBP_MUX_OK) {
|
||||
fprintf(stderr, "ERROR (%s): Could not set XMP chunk.\n",
|
||||
ErrorString(err));
|
||||
goto End;
|
||||
}
|
||||
}
|
||||
|
||||
err = WebPMuxAssemble(mux, &webp_data);
|
||||
if (err != WEBP_MUX_OK) {
|
||||
fprintf(stderr, "ERROR (%s): Could not assemble when re-muxing to add "
|
||||
"loop count/metadata.\n", ErrorString(err));
|
||||
goto End;
|
||||
}
|
||||
}
|
||||
|
||||
if (out_file != NULL) {
|
||||
if (!ImgIoUtilWriteFile((const char*)out_file, webp_data.bytes,
|
||||
webp_data.size)) {
|
||||
WFPRINTF(stderr, "Error writing output file: %s\n", out_file);
|
||||
goto End;
|
||||
}
|
||||
if (!quiet) {
|
||||
if (!WSTRCMP(out_file, "-")) {
|
||||
fprintf(stderr, "Saved %d bytes to STDIO\n",
|
||||
(int)webp_data.size);
|
||||
} else {
|
||||
WFPRINTF(stderr, "Saved output file (%d bytes): %s\n",
|
||||
(int)webp_data.size, out_file);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "Nothing written; use -o flag to save the result "
|
||||
"(%d bytes).\n", (int)webp_data.size);
|
||||
}
|
||||
}
|
||||
|
||||
// All OK.
|
||||
ok = 1;
|
||||
gif_error = GIF_OK;
|
||||
|
||||
End:
|
||||
WebPDataClear(&icc_data);
|
||||
WebPDataClear(&xmp_data);
|
||||
WebPMuxDelete(mux);
|
||||
WebPDataClear(&webp_data);
|
||||
WebPPictureFree(&frame);
|
||||
WebPPictureFree(&curr_canvas);
|
||||
WebPPictureFree(&prev_canvas);
|
||||
WebPAnimEncoderDelete(enc);
|
||||
|
||||
if (gif_error != GIF_OK) {
|
||||
GIFDisplayError(gif, gif_error);
|
||||
}
|
||||
if (gif != NULL) {
|
||||
#if LOCAL_GIF_PREREQ(5,1)
|
||||
DGifCloseFile(gif, &gif_error);
|
||||
#else
|
||||
DGifCloseFile(gif);
|
||||
#endif
|
||||
}
|
||||
|
||||
FREE_WARGV_AND_RETURN(!ok);
|
||||
}
|
||||
|
||||
#else // !WEBP_HAVE_GIF
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
fprintf(stderr, "GIF support not enabled in %s.\n", argv[0]);
|
||||
(void)argc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
416
examples/gifdec.c
Normal file
416
examples/gifdec.c
Normal file
@@ -0,0 +1,416 @@
|
||||
// Copyright 2012 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// GIF decode.
|
||||
|
||||
#include "./gifdec.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef WEBP_HAVE_GIF
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "webp/encode.h"
|
||||
#include "webp/mux_types.h"
|
||||
|
||||
#define GIF_TRANSPARENT_COLOR 0x00000000u
|
||||
#define GIF_WHITE_COLOR 0xffffffffu
|
||||
#define GIF_TRANSPARENT_MASK 0x01
|
||||
#define GIF_DISPOSE_MASK 0x07
|
||||
#define GIF_DISPOSE_SHIFT 2
|
||||
|
||||
// from utils/utils.h
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
extern void WebPCopyPlane(const uint8_t* src, int src_stride,
|
||||
uint8_t* dst, int dst_stride,
|
||||
int width, int height);
|
||||
extern void WebPCopyPixels(const WebPPicture* const src,
|
||||
WebPPicture* const dst);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
void GIFGetBackgroundColor(const ColorMapObject* const color_map,
|
||||
int bgcolor_index, int transparent_index,
|
||||
uint32_t* const bgcolor) {
|
||||
if (transparent_index != GIF_INDEX_INVALID &&
|
||||
bgcolor_index == transparent_index) {
|
||||
*bgcolor = GIF_TRANSPARENT_COLOR; // Special case.
|
||||
} else if (color_map == NULL || color_map->Colors == NULL
|
||||
|| bgcolor_index >= color_map->ColorCount) {
|
||||
*bgcolor = GIF_WHITE_COLOR;
|
||||
fprintf(stderr,
|
||||
"GIF decode warning: invalid background color index. Assuming "
|
||||
"white background.\n");
|
||||
} else {
|
||||
const GifColorType color = color_map->Colors[bgcolor_index];
|
||||
*bgcolor = (0xffu << 24)
|
||||
| (color.Red << 16)
|
||||
| (color.Green << 8)
|
||||
| (color.Blue << 0);
|
||||
}
|
||||
}
|
||||
|
||||
int GIFReadGraphicsExtension(const GifByteType* const buf, int* const duration,
|
||||
GIFDisposeMethod* const dispose,
|
||||
int* const transparent_index) {
|
||||
const int flags = buf[1];
|
||||
const int dispose_raw = (flags >> GIF_DISPOSE_SHIFT) & GIF_DISPOSE_MASK;
|
||||
const int duration_raw = buf[2] | (buf[3] << 8); // In 10 ms units.
|
||||
if (buf[0] != 4) return 0;
|
||||
*duration = duration_raw * 10; // Duration is in 1 ms units.
|
||||
switch (dispose_raw) {
|
||||
case 3:
|
||||
*dispose = GIF_DISPOSE_RESTORE_PREVIOUS;
|
||||
break;
|
||||
case 2:
|
||||
*dispose = GIF_DISPOSE_BACKGROUND;
|
||||
break;
|
||||
case 1:
|
||||
case 0:
|
||||
default:
|
||||
*dispose = GIF_DISPOSE_NONE;
|
||||
break;
|
||||
}
|
||||
*transparent_index =
|
||||
(flags & GIF_TRANSPARENT_MASK) ? buf[4] : GIF_INDEX_INVALID;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int Remap(const GifFileType* const gif, const uint8_t* const src,
|
||||
int len, int transparent_index, uint32_t* dst) {
|
||||
int i;
|
||||
const GifColorType* colors;
|
||||
const ColorMapObject* const cmap =
|
||||
gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap;
|
||||
if (cmap == NULL) return 1;
|
||||
if (cmap->Colors == NULL || cmap->ColorCount <= 0) return 0;
|
||||
colors = cmap->Colors;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (src[i] == transparent_index) {
|
||||
dst[i] = GIF_TRANSPARENT_COLOR;
|
||||
} else if (src[i] < cmap->ColorCount) {
|
||||
const GifColorType c = colors[src[i]];
|
||||
dst[i] = c.Blue | (c.Green << 8) | (c.Red << 16) | (0xffu << 24);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int GIFReadFrame(GifFileType* const gif, int transparent_index,
|
||||
GIFFrameRect* const gif_rect, WebPPicture* const picture) {
|
||||
WebPPicture sub_image;
|
||||
const GifImageDesc* const image_desc = &gif->Image;
|
||||
uint32_t* dst = NULL;
|
||||
uint8_t* tmp = NULL;
|
||||
const GIFFrameRect rect = {
|
||||
image_desc->Left, image_desc->Top, image_desc->Width, image_desc->Height
|
||||
};
|
||||
const uint64_t memory_needed = 4 * rect.width * (uint64_t)rect.height;
|
||||
int ok = 0;
|
||||
*gif_rect = rect;
|
||||
|
||||
if (memory_needed != (size_t)memory_needed || memory_needed > (4ULL << 32)) {
|
||||
fprintf(stderr, "Image is too large (%d x %d).", rect.width, rect.height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Use a view for the sub-picture:
|
||||
if (!WebPPictureView(picture, rect.x_offset, rect.y_offset,
|
||||
rect.width, rect.height, &sub_image)) {
|
||||
fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n",
|
||||
rect.width, rect.height, rect.x_offset, rect.y_offset);
|
||||
return 0;
|
||||
}
|
||||
dst = sub_image.argb;
|
||||
|
||||
tmp = (uint8_t*)WebPMalloc(rect.width * sizeof(*tmp));
|
||||
if (tmp == NULL) goto End;
|
||||
|
||||
if (image_desc->Interlace) { // Interlaced image.
|
||||
// We need 4 passes, with the following offsets and jumps.
|
||||
const int interlace_offsets[] = { 0, 4, 2, 1 };
|
||||
const int interlace_jumps[] = { 8, 8, 4, 2 };
|
||||
int pass;
|
||||
for (pass = 0; pass < 4; ++pass) {
|
||||
const size_t stride = (size_t)sub_image.argb_stride;
|
||||
int y = interlace_offsets[pass];
|
||||
uint32_t* row = dst + y * stride;
|
||||
const size_t jump = interlace_jumps[pass] * stride;
|
||||
for (; y < rect.height; y += interlace_jumps[pass], row += jump) {
|
||||
if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End;
|
||||
if (!Remap(gif, tmp, rect.width, transparent_index, row)) goto End;
|
||||
}
|
||||
}
|
||||
} else { // Non-interlaced image.
|
||||
int y;
|
||||
uint32_t* ptr = dst;
|
||||
for (y = 0; y < rect.height; ++y, ptr += sub_image.argb_stride) {
|
||||
if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End;
|
||||
if (!Remap(gif, tmp, rect.width, transparent_index, ptr)) goto End;
|
||||
}
|
||||
}
|
||||
ok = 1;
|
||||
|
||||
End:
|
||||
if (!ok) picture->error_code = sub_image.error_code;
|
||||
WebPPictureFree(&sub_image);
|
||||
WebPFree(tmp);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int GIFReadLoopCount(GifFileType* const gif, GifByteType** const buf,
|
||||
int* const loop_count) {
|
||||
assert(!memcmp(*buf + 1, "NETSCAPE2.0", 11) ||
|
||||
!memcmp(*buf + 1, "ANIMEXTS1.0", 11));
|
||||
if (DGifGetExtensionNext(gif, buf) == GIF_ERROR) {
|
||||
return 0;
|
||||
}
|
||||
if (*buf == NULL) {
|
||||
return 0; // Loop count sub-block missing.
|
||||
}
|
||||
if ((*buf)[0] < 3 || (*buf)[1] != 1) {
|
||||
return 0; // wrong size/marker
|
||||
}
|
||||
*loop_count = (*buf)[2] | ((*buf)[3] << 8);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int GIFReadMetadata(GifFileType* const gif, GifByteType** const buf,
|
||||
WebPData* const metadata) {
|
||||
const int is_xmp = !memcmp(*buf + 1, "XMP DataXMP", 11);
|
||||
const int is_icc = !memcmp(*buf + 1, "ICCRGBG1012", 11);
|
||||
assert(is_xmp || is_icc);
|
||||
(void)is_icc; // silence unused warning.
|
||||
// Construct metadata from sub-blocks.
|
||||
// Usual case (including ICC profile): In each sub-block, the
|
||||
// first byte specifies its size in bytes (0 to 255) and the
|
||||
// rest of the bytes contain the data.
|
||||
// Special case for XMP data: In each sub-block, the first byte
|
||||
// is also part of the XMP payload. XMP in GIF also has a 257
|
||||
// byte padding data. See the XMP specification for details.
|
||||
while (1) {
|
||||
WebPData subblock;
|
||||
const uint8_t* tmp;
|
||||
if (DGifGetExtensionNext(gif, buf) == GIF_ERROR) {
|
||||
return 0;
|
||||
}
|
||||
if (*buf == NULL) break; // Finished.
|
||||
subblock.size = is_xmp ? (*buf)[0] + 1 : (*buf)[0];
|
||||
assert(subblock.size > 0);
|
||||
subblock.bytes = is_xmp ? *buf : *buf + 1;
|
||||
// Note: We store returned value in 'tmp' first, to avoid
|
||||
// leaking old memory in metadata->bytes on error.
|
||||
tmp = (uint8_t*)realloc((void*)metadata->bytes,
|
||||
metadata->size + subblock.size);
|
||||
if (tmp == NULL) {
|
||||
return 0;
|
||||
}
|
||||
memcpy((void*)(tmp + metadata->size),
|
||||
subblock.bytes, subblock.size);
|
||||
metadata->bytes = tmp;
|
||||
metadata->size += subblock.size;
|
||||
}
|
||||
if (is_xmp) {
|
||||
// XMP padding data is 0x01, 0xff, 0xfe ... 0x01, 0x00.
|
||||
const size_t xmp_pading_size = 257;
|
||||
if (metadata->size > xmp_pading_size) {
|
||||
metadata->size -= xmp_pading_size;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void ClearRectangle(WebPPicture* const picture,
|
||||
int left, int top, int width, int height) {
|
||||
int i, j;
|
||||
const size_t stride = picture->argb_stride;
|
||||
uint32_t* dst = picture->argb + top * stride + left;
|
||||
for (j = 0; j < height; ++j, dst += stride) {
|
||||
for (i = 0; i < width; ++i) dst[i] = GIF_TRANSPARENT_COLOR;
|
||||
}
|
||||
}
|
||||
|
||||
void GIFClearPic(WebPPicture* const pic, const GIFFrameRect* const rect) {
|
||||
if (rect != NULL) {
|
||||
ClearRectangle(pic, rect->x_offset, rect->y_offset,
|
||||
rect->width, rect->height);
|
||||
} else {
|
||||
ClearRectangle(pic, 0, 0, pic->width, pic->height);
|
||||
}
|
||||
}
|
||||
|
||||
void GIFCopyPixels(const WebPPicture* const src, WebPPicture* const dst) {
|
||||
WebPCopyPixels(src, dst);
|
||||
}
|
||||
|
||||
void GIFDisposeFrame(GIFDisposeMethod dispose, const GIFFrameRect* const rect,
|
||||
const WebPPicture* const prev_canvas,
|
||||
WebPPicture* const curr_canvas) {
|
||||
assert(rect != NULL);
|
||||
if (dispose == GIF_DISPOSE_BACKGROUND) {
|
||||
GIFClearPic(curr_canvas, rect);
|
||||
} else if (dispose == GIF_DISPOSE_RESTORE_PREVIOUS) {
|
||||
const size_t src_stride = prev_canvas->argb_stride;
|
||||
const uint32_t* const src = prev_canvas->argb + rect->x_offset
|
||||
+ rect->y_offset * src_stride;
|
||||
const size_t dst_stride = curr_canvas->argb_stride;
|
||||
uint32_t* const dst = curr_canvas->argb + rect->x_offset
|
||||
+ rect->y_offset * dst_stride;
|
||||
assert(prev_canvas != NULL);
|
||||
WebPCopyPlane((uint8_t*)src, (int)(4 * src_stride),
|
||||
(uint8_t*)dst, (int)(4 * dst_stride),
|
||||
4 * rect->width, rect->height);
|
||||
}
|
||||
}
|
||||
|
||||
void GIFBlendFrames(const WebPPicture* const src,
|
||||
const GIFFrameRect* const rect, WebPPicture* const dst) {
|
||||
int i, j;
|
||||
const size_t src_stride = src->argb_stride;
|
||||
const size_t dst_stride = dst->argb_stride;
|
||||
assert(src->width == dst->width && src->height == dst->height);
|
||||
for (j = rect->y_offset; j < rect->y_offset + rect->height; ++j) {
|
||||
for (i = rect->x_offset; i < rect->x_offset + rect->width; ++i) {
|
||||
const uint32_t src_pixel = src->argb[j * src_stride + i];
|
||||
const int src_alpha = src_pixel >> 24;
|
||||
if (src_alpha != 0) {
|
||||
dst->argb[j * dst_stride + i] = src_pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GIFDisplayError(const GifFileType* const gif, int gif_error) {
|
||||
// libgif 4.2.0 has retired PrintGifError() and added GifErrorString().
|
||||
#if LOCAL_GIF_PREREQ(4,2)
|
||||
#if LOCAL_GIF_PREREQ(5,0)
|
||||
// Static string actually, hence the const char* cast.
|
||||
const char* error_str = (const char*)GifErrorString(
|
||||
(gif == NULL) ? gif_error : gif->Error);
|
||||
#else
|
||||
const char* error_str = (const char*)GifErrorString();
|
||||
(void)gif;
|
||||
#endif
|
||||
if (error_str == NULL) error_str = "Unknown error";
|
||||
fprintf(stderr, "GIFLib Error %d: %s\n", gif_error, error_str);
|
||||
#else
|
||||
(void)gif;
|
||||
fprintf(stderr, "GIFLib Error %d: ", gif_error);
|
||||
PrintGifError();
|
||||
fprintf(stderr, "\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
#else // !WEBP_HAVE_GIF
|
||||
|
||||
static void ErrorGIFNotAvailable() {
|
||||
fprintf(stderr, "GIF support not compiled. Please install the libgif-dev "
|
||||
"package before building.\n");
|
||||
}
|
||||
|
||||
void GIFGetBackgroundColor(const struct ColorMapObject* const color_map,
|
||||
int bgcolor_index, int transparent_index,
|
||||
uint32_t* const bgcolor) {
|
||||
(void)color_map;
|
||||
(void)bgcolor_index;
|
||||
(void)transparent_index;
|
||||
(void)bgcolor;
|
||||
ErrorGIFNotAvailable();
|
||||
}
|
||||
|
||||
int GIFReadGraphicsExtension(const GifByteType* const data, int* const duration,
|
||||
GIFDisposeMethod* const dispose,
|
||||
int* const transparent_index) {
|
||||
(void)data;
|
||||
(void)duration;
|
||||
(void)dispose;
|
||||
(void)transparent_index;
|
||||
ErrorGIFNotAvailable();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GIFReadFrame(struct GifFileType* const gif, int transparent_index,
|
||||
GIFFrameRect* const gif_rect,
|
||||
struct WebPPicture* const picture) {
|
||||
(void)gif;
|
||||
(void)transparent_index;
|
||||
(void)gif_rect;
|
||||
(void)picture;
|
||||
ErrorGIFNotAvailable();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GIFReadLoopCount(struct GifFileType* const gif, GifByteType** const buf,
|
||||
int* const loop_count) {
|
||||
(void)gif;
|
||||
(void)buf;
|
||||
(void)loop_count;
|
||||
ErrorGIFNotAvailable();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GIFReadMetadata(struct GifFileType* const gif, GifByteType** const buf,
|
||||
struct WebPData* const metadata) {
|
||||
(void)gif;
|
||||
(void)buf;
|
||||
(void)metadata;
|
||||
ErrorGIFNotAvailable();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GIFDisposeFrame(GIFDisposeMethod dispose, const GIFFrameRect* const rect,
|
||||
const struct WebPPicture* const prev_canvas,
|
||||
struct WebPPicture* const curr_canvas) {
|
||||
(void)dispose;
|
||||
(void)rect;
|
||||
(void)prev_canvas;
|
||||
(void)curr_canvas;
|
||||
ErrorGIFNotAvailable();
|
||||
}
|
||||
|
||||
void GIFBlendFrames(const struct WebPPicture* const src,
|
||||
const GIFFrameRect* const rect,
|
||||
struct WebPPicture* const dst) {
|
||||
(void)src;
|
||||
(void)rect;
|
||||
(void)dst;
|
||||
ErrorGIFNotAvailable();
|
||||
}
|
||||
|
||||
void GIFDisplayError(const struct GifFileType* const gif, int gif_error) {
|
||||
(void)gif;
|
||||
(void)gif_error;
|
||||
ErrorGIFNotAvailable();
|
||||
}
|
||||
|
||||
void GIFClearPic(struct WebPPicture* const pic,
|
||||
const GIFFrameRect* const rect) {
|
||||
(void)pic;
|
||||
(void)rect;
|
||||
ErrorGIFNotAvailable();
|
||||
}
|
||||
|
||||
void GIFCopyPixels(const struct WebPPicture* const src,
|
||||
struct WebPPicture* const dst) {
|
||||
(void)src;
|
||||
(void)dst;
|
||||
ErrorGIFNotAvailable();
|
||||
}
|
||||
|
||||
#endif // WEBP_HAVE_GIF
|
||||
|
||||
// -----------------------------------------------------------------------------
|
116
examples/gifdec.h
Normal file
116
examples/gifdec.h
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2014 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// GIF decode.
|
||||
|
||||
#ifndef WEBP_EXAMPLES_GIFDEC_H_
|
||||
#define WEBP_EXAMPLES_GIFDEC_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include "webp/types.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "webp/config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WEBP_HAVE_GIF
|
||||
#include <gif_lib.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// GIFLIB_MAJOR is only defined in libgif >= 4.2.0.
|
||||
#if defined(GIFLIB_MAJOR) && defined(GIFLIB_MINOR)
|
||||
# define LOCAL_GIF_VERSION ((GIFLIB_MAJOR << 8) | GIFLIB_MINOR)
|
||||
# define LOCAL_GIF_PREREQ(maj, min) \
|
||||
(LOCAL_GIF_VERSION >= (((maj) << 8) | (min)))
|
||||
#else
|
||||
# define LOCAL_GIF_VERSION 0
|
||||
# define LOCAL_GIF_PREREQ(maj, min) 0
|
||||
#endif
|
||||
|
||||
#define GIF_INDEX_INVALID (-1)
|
||||
|
||||
typedef enum GIFDisposeMethod {
|
||||
GIF_DISPOSE_NONE,
|
||||
GIF_DISPOSE_BACKGROUND,
|
||||
GIF_DISPOSE_RESTORE_PREVIOUS
|
||||
} GIFDisposeMethod;
|
||||
|
||||
typedef struct {
|
||||
int x_offset, y_offset, width, height;
|
||||
} GIFFrameRect;
|
||||
|
||||
struct WebPData;
|
||||
struct WebPPicture;
|
||||
|
||||
#ifndef WEBP_HAVE_GIF
|
||||
struct ColorMapObject;
|
||||
struct GifFileType;
|
||||
typedef unsigned char GifByteType;
|
||||
#endif
|
||||
|
||||
// Given the index of background color and transparent color, returns the
|
||||
// corresponding background color (in BGRA format) in 'bgcolor'.
|
||||
void GIFGetBackgroundColor(const struct ColorMapObject* const color_map,
|
||||
int bgcolor_index, int transparent_index,
|
||||
uint32_t* const bgcolor);
|
||||
|
||||
// Parses the given graphics extension data to get frame duration (in 1ms
|
||||
// units), dispose method and transparent color index.
|
||||
// Returns true on success.
|
||||
int GIFReadGraphicsExtension(const GifByteType* const buf, int* const duration,
|
||||
GIFDisposeMethod* const dispose,
|
||||
int* const transparent_index);
|
||||
|
||||
// Reads the next GIF frame from 'gif' into 'picture'. Also, returns the GIF
|
||||
// frame dimensions and offsets in 'rect'.
|
||||
// Returns true on success.
|
||||
int GIFReadFrame(struct GifFileType* const gif, int transparent_index,
|
||||
GIFFrameRect* const gif_rect,
|
||||
struct WebPPicture* const picture);
|
||||
|
||||
// Parses loop count from the given Netscape extension data.
|
||||
int GIFReadLoopCount(struct GifFileType* const gif, GifByteType** const buf,
|
||||
int* const loop_count);
|
||||
|
||||
// Parses the given ICC or XMP extension data and stores it into 'metadata'.
|
||||
// Returns true on success.
|
||||
int GIFReadMetadata(struct GifFileType* const gif, GifByteType** const buf,
|
||||
struct WebPData* const metadata);
|
||||
|
||||
// Dispose the pixels within 'rect' of 'curr_canvas' based on 'dispose' method
|
||||
// and 'prev_canvas'.
|
||||
void GIFDisposeFrame(GIFDisposeMethod dispose, const GIFFrameRect* const rect,
|
||||
const struct WebPPicture* const prev_canvas,
|
||||
struct WebPPicture* const curr_canvas);
|
||||
|
||||
// Given 'src' picture and its frame rectangle 'rect', blend it into 'dst'.
|
||||
void GIFBlendFrames(const struct WebPPicture* const src,
|
||||
const GIFFrameRect* const rect,
|
||||
struct WebPPicture* const dst);
|
||||
|
||||
// Prints an error string based on 'gif_error'.
|
||||
void GIFDisplayError(const struct GifFileType* const gif, int gif_error);
|
||||
|
||||
// In the given 'pic', clear the pixels in 'rect' to transparent color.
|
||||
void GIFClearPic(struct WebPPicture* const pic, const GIFFrameRect* const rect);
|
||||
|
||||
// Copy pixels from 'src' to 'dst' honoring strides. 'src' and 'dst' are assumed
|
||||
// to be already allocated.
|
||||
void GIFCopyPixels(const struct WebPPicture* const src,
|
||||
struct WebPPicture* const dst);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // WEBP_EXAMPLES_GIFDEC_H_
|
339
examples/img2webp.c
Normal file
339
examples/img2webp.c
Normal file
@@ -0,0 +1,339 @@
|
||||
// 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// generate an animated WebP out of a sequence of images
|
||||
// (PNG, JPEG, ...)
|
||||
//
|
||||
// Example usage:
|
||||
// img2webp -o out.webp -q 40 -mixed -duration 40 input??.png
|
||||
//
|
||||
// Author: skal@google.com (Pascal Massimino)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "webp/config.h"
|
||||
#endif
|
||||
|
||||
#include "../examples/example_util.h"
|
||||
#include "../imageio/image_dec.h"
|
||||
#include "../imageio/imageio_util.h"
|
||||
#include "./stopwatch.h"
|
||||
#include "./unicode.h"
|
||||
#include "sharpyuv/sharpyuv.h"
|
||||
#include "webp/encode.h"
|
||||
#include "webp/mux.h"
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static void Help(void) {
|
||||
printf("Usage:\n\n");
|
||||
printf(" img2webp [file_options] [[frame_options] frame_file]...");
|
||||
printf(" [-o webp_file]\n\n");
|
||||
|
||||
printf("File-level options (only used at the start of compression):\n");
|
||||
printf(" -min_size ............ minimize size\n");
|
||||
printf(" -kmax <int> .......... maximum number of frame between key-frames\n"
|
||||
" (0=only keyframes)\n");
|
||||
printf(" -kmin <int> .......... minimum number of frame between key-frames\n"
|
||||
" (0=disable key-frames altogether)\n");
|
||||
printf(" -mixed ............... use mixed lossy/lossless automatic mode\n");
|
||||
printf(" -near_lossless <int> . use near-lossless image preprocessing\n"
|
||||
" (0..100=off), default=100\n");
|
||||
printf(" -sharp_yuv ........... use sharper (and slower) RGB->YUV "
|
||||
"conversion\n "
|
||||
"(lossy only)\n");
|
||||
printf(" -loop <int> .......... loop count (default: 0, = infinite loop)\n");
|
||||
printf(" -v ................... verbose mode\n");
|
||||
printf(" -h ................... this help\n");
|
||||
printf(" -version ............. print version number and exit\n");
|
||||
printf("\n");
|
||||
|
||||
printf("Per-frame options (only used for subsequent images input):\n");
|
||||
printf(" -d <int> ............. frame duration in ms (default: 100)\n");
|
||||
printf(" -lossless ........... use lossless mode (default)\n");
|
||||
printf(" -lossy ... ........... use lossy mode\n");
|
||||
printf(" -q <float> ........... quality\n");
|
||||
printf(" -m <int> ............. method to use\n");
|
||||
|
||||
printf("\n");
|
||||
printf("example: img2webp -loop 2 in0.png -lossy in1.jpg\n"
|
||||
" -d 80 in2.tiff -o out.webp\n");
|
||||
printf("\nNote: if a single file name is passed as the argument, the "
|
||||
"arguments will be\n");
|
||||
printf("tokenized from this file. The file name must not start with "
|
||||
"the character '-'.\n");
|
||||
printf("\nSupported input formats:\n %s\n",
|
||||
WebPGetEnabledInputFileFormats());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static int ReadImage(const char filename[], WebPPicture* const pic) {
|
||||
const uint8_t* data = NULL;
|
||||
size_t data_size = 0;
|
||||
WebPImageReader reader;
|
||||
int ok;
|
||||
#ifdef HAVE_WINCODEC_H
|
||||
// Try to decode the file using WIC falling back to the other readers for
|
||||
// e.g., WebP.
|
||||
ok = ReadPictureWithWIC(filename, pic, 1, NULL);
|
||||
if (ok) return 1;
|
||||
#endif
|
||||
if (!ImgIoUtilReadFile(filename, &data, &data_size)) return 0;
|
||||
reader = WebPGuessImageReader(data, data_size);
|
||||
ok = reader(data, data_size, pic, 1, NULL);
|
||||
WebPFree((void*)data);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int SetLoopCount(int loop_count, WebPData* const webp_data) {
|
||||
int ok = 1;
|
||||
WebPMuxError err;
|
||||
uint32_t features;
|
||||
WebPMuxAnimParams new_params;
|
||||
WebPMux* const mux = WebPMuxCreate(webp_data, 1);
|
||||
if (mux == NULL) return 0;
|
||||
|
||||
err = WebPMuxGetFeatures(mux, &features);
|
||||
ok = (err == WEBP_MUX_OK);
|
||||
if (!ok || !(features & ANIMATION_FLAG)) goto End;
|
||||
|
||||
err = WebPMuxGetAnimationParams(mux, &new_params);
|
||||
ok = (err == WEBP_MUX_OK);
|
||||
if (ok) {
|
||||
new_params.loop_count = loop_count;
|
||||
err = WebPMuxSetAnimationParams(mux, &new_params);
|
||||
ok = (err == WEBP_MUX_OK);
|
||||
}
|
||||
if (ok) {
|
||||
WebPDataClear(webp_data);
|
||||
err = WebPMuxAssemble(mux, webp_data);
|
||||
ok = (err == WEBP_MUX_OK);
|
||||
}
|
||||
|
||||
End:
|
||||
WebPMuxDelete(mux);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Error during loop-count setting\n");
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
const char* output = NULL;
|
||||
WebPAnimEncoder* enc = NULL;
|
||||
int verbose = 0;
|
||||
int pic_num = 0;
|
||||
int duration = 100;
|
||||
int timestamp_ms = 0;
|
||||
int loop_count = 0;
|
||||
int width = 0, height = 0;
|
||||
WebPAnimEncoderOptions anim_config;
|
||||
WebPConfig config;
|
||||
WebPPicture pic;
|
||||
WebPData webp_data;
|
||||
int c;
|
||||
int have_input = 0;
|
||||
CommandLineArguments cmd_args;
|
||||
int ok;
|
||||
|
||||
INIT_WARGV(argc, argv);
|
||||
|
||||
ok = ExUtilInitCommandLineArguments(argc - 1, argv + 1, &cmd_args);
|
||||
if (!ok) FREE_WARGV_AND_RETURN(1);
|
||||
|
||||
argc = cmd_args.argc_;
|
||||
argv = cmd_args.argv_;
|
||||
|
||||
WebPDataInit(&webp_data);
|
||||
if (!WebPAnimEncoderOptionsInit(&anim_config) ||
|
||||
!WebPConfigInit(&config) ||
|
||||
!WebPPictureInit(&pic)) {
|
||||
fprintf(stderr, "Library version mismatch!\n");
|
||||
ok = 0;
|
||||
goto End;
|
||||
}
|
||||
|
||||
// 1st pass of option parsing
|
||||
for (c = 0; ok && c < argc; ++c) {
|
||||
if (argv[c][0] == '-') {
|
||||
int parse_error = 0;
|
||||
if (!strcmp(argv[c], "-o") && c + 1 < argc) {
|
||||
argv[c] = NULL;
|
||||
output = (const char*)GET_WARGV_SHIFTED(argv, ++c);
|
||||
} else if (!strcmp(argv[c], "-kmin") && c + 1 < argc) {
|
||||
argv[c] = NULL;
|
||||
anim_config.kmin = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else if (!strcmp(argv[c], "-kmax") && c + 1 < argc) {
|
||||
argv[c] = NULL;
|
||||
anim_config.kmax = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else if (!strcmp(argv[c], "-loop") && c + 1 < argc) {
|
||||
argv[c] = NULL;
|
||||
loop_count = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
if (loop_count < 0) {
|
||||
fprintf(stderr, "Invalid non-positive loop-count (%d)\n", loop_count);
|
||||
parse_error = 1;
|
||||
}
|
||||
} else if (!strcmp(argv[c], "-min_size")) {
|
||||
anim_config.minimize_size = 1;
|
||||
} else if (!strcmp(argv[c], "-mixed")) {
|
||||
anim_config.allow_mixed = 1;
|
||||
config.lossless = 0;
|
||||
} else if (!strcmp(argv[c], "-near_lossless") && c + 1 < argc) {
|
||||
argv[c] = NULL;
|
||||
config.near_lossless = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else if (!strcmp(argv[c], "-sharp_yuv")) {
|
||||
config.use_sharp_yuv = 1;
|
||||
} else if (!strcmp(argv[c], "-v")) {
|
||||
verbose = 1;
|
||||
} else if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else if (!strcmp(argv[c], "-version")) {
|
||||
const int enc_version = WebPGetEncoderVersion();
|
||||
const int mux_version = WebPGetMuxVersion();
|
||||
const int sharpyuv_version = SharpYuvGetVersion();
|
||||
printf("WebP Encoder version: %d.%d.%d\nWebP Mux version: %d.%d.%d\n",
|
||||
(enc_version >> 16) & 0xff, (enc_version >> 8) & 0xff,
|
||||
enc_version & 0xff, (mux_version >> 16) & 0xff,
|
||||
(mux_version >> 8) & 0xff, mux_version & 0xff);
|
||||
printf("libsharpyuv: %d.%d.%d\n", (sharpyuv_version >> 24) & 0xff,
|
||||
(sharpyuv_version >> 16) & 0xffff, sharpyuv_version & 0xff);
|
||||
goto End;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
ok = !parse_error;
|
||||
if (!ok) goto End;
|
||||
argv[c] = NULL; // mark option as 'parsed' during 1st pass
|
||||
} else {
|
||||
have_input |= 1;
|
||||
}
|
||||
}
|
||||
if (!have_input) {
|
||||
fprintf(stderr, "No input file(s) for generating animation!\n");
|
||||
goto End;
|
||||
}
|
||||
|
||||
// image-reading pass
|
||||
pic_num = 0;
|
||||
config.lossless = 1;
|
||||
for (c = 0; ok && c < argc; ++c) {
|
||||
if (argv[c] == NULL) continue;
|
||||
if (argv[c][0] == '-') { // parse local options
|
||||
int parse_error = 0;
|
||||
if (!strcmp(argv[c], "-lossy")) {
|
||||
if (!anim_config.allow_mixed) config.lossless = 0;
|
||||
} else if (!strcmp(argv[c], "-lossless")) {
|
||||
if (!anim_config.allow_mixed) config.lossless = 1;
|
||||
} else if (!strcmp(argv[c], "-q") && c + 1 < argc) {
|
||||
config.quality = ExUtilGetFloat(argv[++c], &parse_error);
|
||||
} else if (!strcmp(argv[c], "-m") && c + 1 < argc) {
|
||||
config.method = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else if (!strcmp(argv[c], "-d") && c + 1 < argc) {
|
||||
duration = ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
if (duration <= 0) {
|
||||
fprintf(stderr, "Invalid negative duration (%d)\n", duration);
|
||||
parse_error = 1;
|
||||
}
|
||||
} else {
|
||||
parse_error = 1; // shouldn't be here.
|
||||
fprintf(stderr, "Unknown option [%s]\n", argv[c]);
|
||||
}
|
||||
ok = !parse_error;
|
||||
if (!ok) goto End;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
ok = WebPValidateConfig(&config);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Invalid configuration.\n");
|
||||
goto End;
|
||||
}
|
||||
}
|
||||
|
||||
// read next input image
|
||||
pic.use_argb = 1;
|
||||
ok = ReadImage((const char*)GET_WARGV_SHIFTED(argv, c), &pic);
|
||||
if (!ok) goto End;
|
||||
|
||||
if (enc == NULL) {
|
||||
width = pic.width;
|
||||
height = pic.height;
|
||||
enc = WebPAnimEncoderNew(width, height, &anim_config);
|
||||
ok = (enc != NULL);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Could not create WebPAnimEncoder object.\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
ok = (width == pic.width && height == pic.height);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Frame #%d dimension mismatched! "
|
||||
"Got %d x %d. Was expecting %d x %d.\n",
|
||||
pic_num, pic.width, pic.height, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
ok = WebPAnimEncoderAdd(enc, &pic, timestamp_ms, &config);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Error while adding frame #%d\n", pic_num);
|
||||
}
|
||||
}
|
||||
WebPPictureFree(&pic);
|
||||
if (!ok) goto End;
|
||||
|
||||
if (verbose) {
|
||||
WFPRINTF(stderr, "Added frame #%3d at time %4d (file: %s)\n",
|
||||
pic_num, timestamp_ms, GET_WARGV_SHIFTED(argv, c));
|
||||
}
|
||||
timestamp_ms += duration;
|
||||
++pic_num;
|
||||
}
|
||||
|
||||
// add a last fake frame to signal the last duration
|
||||
ok = ok && WebPAnimEncoderAdd(enc, NULL, timestamp_ms, NULL);
|
||||
ok = ok && WebPAnimEncoderAssemble(enc, &webp_data);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Error during final animation assembly.\n");
|
||||
}
|
||||
|
||||
End:
|
||||
// free resources
|
||||
WebPAnimEncoderDelete(enc);
|
||||
|
||||
if (ok && loop_count > 0) { // Re-mux to add loop count.
|
||||
ok = SetLoopCount(loop_count, &webp_data);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
if (output != NULL) {
|
||||
ok = ImgIoUtilWriteFile(output, webp_data.bytes, webp_data.size);
|
||||
if (ok) WFPRINTF(stderr, "output file: %s ", (const W_CHAR*)output);
|
||||
} else {
|
||||
fprintf(stderr, "[no output file specified] ");
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
fprintf(stderr, "[%d frames, %u bytes].\n",
|
||||
pic_num, (unsigned int)webp_data.size);
|
||||
}
|
||||
WebPDataClear(&webp_data);
|
||||
ExUtilDeleteCommandLineArguments(&cmd_args);
|
||||
FREE_WARGV_AND_RETURN(ok ? 0 : 1);
|
||||
}
|
63
examples/stopwatch.h
Normal file
63
examples/stopwatch.h
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2011 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Helper functions to measure elapsed time.
|
||||
//
|
||||
// Author: Mikolaj Zalewski (mikolajz@google.com)
|
||||
|
||||
#ifndef WEBP_EXAMPLES_STOPWATCH_H_
|
||||
#define WEBP_EXAMPLES_STOPWATCH_H_
|
||||
|
||||
#include "webp/types.h"
|
||||
|
||||
#if defined _WIN32 && !defined __GNUC__
|
||||
#include <windows.h>
|
||||
|
||||
typedef LARGE_INTEGER Stopwatch;
|
||||
|
||||
static WEBP_INLINE void StopwatchReset(Stopwatch* watch) {
|
||||
QueryPerformanceCounter(watch);
|
||||
}
|
||||
|
||||
static WEBP_INLINE double StopwatchReadAndReset(Stopwatch* watch) {
|
||||
const LARGE_INTEGER old_value = *watch;
|
||||
LARGE_INTEGER freq;
|
||||
if (!QueryPerformanceCounter(watch))
|
||||
return 0.0;
|
||||
if (!QueryPerformanceFrequency(&freq))
|
||||
return 0.0;
|
||||
if (freq.QuadPart == 0)
|
||||
return 0.0;
|
||||
return (watch->QuadPart - old_value.QuadPart) / (double)freq.QuadPart;
|
||||
}
|
||||
|
||||
|
||||
#else /* !_WIN32 */
|
||||
#include <string.h> // memcpy
|
||||
#include <sys/time.h>
|
||||
|
||||
typedef struct timeval Stopwatch;
|
||||
|
||||
static WEBP_INLINE void StopwatchReset(Stopwatch* watch) {
|
||||
gettimeofday(watch, NULL);
|
||||
}
|
||||
|
||||
static WEBP_INLINE double StopwatchReadAndReset(Stopwatch* watch) {
|
||||
struct timeval old_value;
|
||||
double delta_sec, delta_usec;
|
||||
memcpy(&old_value, watch, sizeof(old_value));
|
||||
gettimeofday(watch, NULL);
|
||||
delta_sec = (double)watch->tv_sec - old_value.tv_sec;
|
||||
delta_usec = (double)watch->tv_usec - old_value.tv_usec;
|
||||
return delta_sec + delta_usec / 1000000.0;
|
||||
}
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#endif // WEBP_EXAMPLES_STOPWATCH_H_
|
BIN
examples/test.webp
Normal file
BIN
examples/test.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
4
examples/test_ref.ppm
Normal file
4
examples/test_ref.ppm
Normal file
File diff suppressed because one or more lines are too long
116
examples/unicode.h
Normal file
116
examples/unicode.h
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2018 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Unicode support for Windows. The main idea is to maintain an array of Unicode
|
||||
// arguments (wargv) and use it only for file paths. The regular argv is used
|
||||
// for everything else.
|
||||
//
|
||||
// Author: Yannis Guyon (yguyon@google.com)
|
||||
|
||||
#ifndef WEBP_EXAMPLES_UNICODE_H_
|
||||
#define WEBP_EXAMPLES_UNICODE_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(_WIN32) && defined(_UNICODE)
|
||||
|
||||
// wchar_t is used instead of TCHAR because we only perform additional work when
|
||||
// Unicode is enabled and because the output of CommandLineToArgvW() is wchar_t.
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <wchar.h>
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
// Create a wchar_t array containing Unicode parameters.
|
||||
#define INIT_WARGV(ARGC, ARGV) \
|
||||
int wargc; \
|
||||
const W_CHAR** const wargv = \
|
||||
(const W_CHAR**)CommandLineToArgvW(GetCommandLineW(), &wargc); \
|
||||
do { \
|
||||
if (wargv == NULL || wargc != (ARGC)) { \
|
||||
fprintf(stderr, "Error: Unable to get Unicode arguments.\n"); \
|
||||
FREE_WARGV_AND_RETURN(-1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Use this to get a Unicode argument (e.g. file path).
|
||||
#define GET_WARGV(UNUSED, C) wargv[C]
|
||||
// For cases where argv is shifted by one compared to wargv.
|
||||
#define GET_WARGV_SHIFTED(UNUSED, C) wargv[(C) + 1]
|
||||
#define GET_WARGV_OR_NULL() wargv
|
||||
|
||||
// Release resources. LocalFree() is needed after CommandLineToArgvW().
|
||||
#define FREE_WARGV() LOCAL_FREE((W_CHAR** const)wargv)
|
||||
#define LOCAL_FREE(WARGV) \
|
||||
do { \
|
||||
if ((WARGV) != NULL) LocalFree(WARGV); \
|
||||
} while (0)
|
||||
|
||||
#define W_CHAR wchar_t // WCHAR without underscore might already be defined.
|
||||
#define TO_W_CHAR(STR) (L##STR)
|
||||
|
||||
#define WFOPEN(ARG, OPT) _wfopen((const W_CHAR*)ARG, TO_W_CHAR(OPT))
|
||||
|
||||
#define WFPRINTF(STREAM, STR, ...) \
|
||||
do { \
|
||||
int prev_mode; \
|
||||
fflush(STREAM); \
|
||||
prev_mode = _setmode(_fileno(STREAM), _O_U8TEXT); \
|
||||
fwprintf(STREAM, TO_W_CHAR(STR), __VA_ARGS__); \
|
||||
fflush(STREAM); \
|
||||
(void)_setmode(_fileno(STREAM), prev_mode); \
|
||||
} while (0)
|
||||
#define WPRINTF(STR, ...) WFPRINTF(stdout, STR, __VA_ARGS__)
|
||||
|
||||
#define WSTRLEN(FILENAME) wcslen((const W_CHAR*)FILENAME)
|
||||
#define WSTRCMP(FILENAME, STR) wcscmp((const W_CHAR*)FILENAME, TO_W_CHAR(STR))
|
||||
#define WSTRRCHR(FILENAME, STR) wcsrchr((const W_CHAR*)FILENAME, TO_W_CHAR(STR))
|
||||
#define WSNPRINTF(A, B, STR, ...) _snwprintf(A, B, TO_W_CHAR(STR), __VA_ARGS__)
|
||||
|
||||
#else
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// Unicode file paths work as is on Unix platforms, and no extra work is done on
|
||||
// Windows either if Unicode is disabled.
|
||||
|
||||
#define INIT_WARGV(ARGC, ARGV)
|
||||
|
||||
#define GET_WARGV(ARGV, C) (ARGV)[C]
|
||||
#define GET_WARGV_SHIFTED(ARGV, C) (ARGV)[C]
|
||||
#define GET_WARGV_OR_NULL() NULL
|
||||
|
||||
#define FREE_WARGV()
|
||||
#define LOCAL_FREE(WARGV)
|
||||
|
||||
#define W_CHAR char
|
||||
#define TO_W_CHAR(STR) (STR)
|
||||
|
||||
#define WFOPEN(ARG, OPT) fopen(ARG, OPT)
|
||||
|
||||
#define WPRINTF(STR, ...) printf(STR, __VA_ARGS__)
|
||||
#define WFPRINTF(STREAM, STR, ...) fprintf(STREAM, STR, __VA_ARGS__)
|
||||
|
||||
#define WSTRLEN(FILENAME) strlen(FILENAME)
|
||||
#define WSTRCMP(FILENAME, STR) strcmp(FILENAME, STR)
|
||||
#define WSTRRCHR(FILENAME, STR) strrchr(FILENAME, STR)
|
||||
#define WSNPRINTF(A, B, STR, ...) snprintf(A, B, STR, __VA_ARGS__)
|
||||
|
||||
#endif // defined(_WIN32) && defined(_UNICODE)
|
||||
|
||||
// Don't forget to free wargv before returning (e.g. from main).
|
||||
#define FREE_WARGV_AND_RETURN(VALUE) \
|
||||
do { \
|
||||
FREE_WARGV(); \
|
||||
return (VALUE); \
|
||||
} while (0)
|
||||
|
||||
#endif // WEBP_EXAMPLES_UNICODE_H_
|
76
examples/unicode_gif.h
Normal file
76
examples/unicode_gif.h
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2018 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// giflib doesn't have a Unicode DGifOpenFileName(). Let's make one.
|
||||
//
|
||||
// Author: Yannis Guyon (yguyon@google.com)
|
||||
|
||||
#ifndef WEBP_EXAMPLES_UNICODE_GIF_H_
|
||||
#define WEBP_EXAMPLES_UNICODE_GIF_H_
|
||||
|
||||
#include "./unicode.h"
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "webp/config.h" // For WEBP_HAVE_GIF
|
||||
#endif
|
||||
|
||||
#if defined(WEBP_HAVE_GIF)
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h> // Not standard, needed for _topen and flags.
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
#include <gif_lib.h>
|
||||
#include <string.h>
|
||||
#include "./gifdec.h"
|
||||
|
||||
#if !defined(STDIN_FILENO)
|
||||
#define STDIN_FILENO 0
|
||||
#endif
|
||||
|
||||
static GifFileType* DGifOpenFileUnicode(const W_CHAR* file_name, int* error) {
|
||||
if (!WSTRCMP(file_name, "-")) {
|
||||
#if LOCAL_GIF_PREREQ(5, 0)
|
||||
return DGifOpenFileHandle(STDIN_FILENO, error);
|
||||
#else
|
||||
(void)error;
|
||||
return DGifOpenFileHandle(STDIN_FILENO);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && defined(_UNICODE)
|
||||
{
|
||||
int file_handle = _wopen(file_name, _O_RDONLY | _O_BINARY);
|
||||
if (file_handle == -1) {
|
||||
if (error != NULL) *error = D_GIF_ERR_OPEN_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if LOCAL_GIF_PREREQ(5, 0)
|
||||
return DGifOpenFileHandle(file_handle, error);
|
||||
#else
|
||||
return DGifOpenFileHandle(file_handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#if LOCAL_GIF_PREREQ(5, 0)
|
||||
return DGifOpenFileName(file_name, error);
|
||||
#else
|
||||
return DGifOpenFileName(file_name);
|
||||
#endif
|
||||
|
||||
#endif // defined(_WIN32) && defined(_UNICODE)
|
||||
// DGifCloseFile() is called later.
|
||||
}
|
||||
|
||||
#endif // defined(WEBP_HAVE_GIF)
|
||||
|
||||
#endif // WEBP_EXAMPLES_UNICODE_GIF_H_
|
654
examples/vwebp.c
Normal file
654
examples/vwebp.c
Normal file
@@ -0,0 +1,654 @@
|
||||
// Copyright 2011 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.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// Simple OpenGL-based WebP file viewer.
|
||||
//
|
||||
// Author: Skal (pascal.massimino@gmail.com)
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "webp/config.h"
|
||||
#endif
|
||||
|
||||
#if defined(__unix__) || defined(__CYGWIN__)
|
||||
#define _POSIX_C_SOURCE 200112L // for setenv
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(WEBP_HAVE_GL)
|
||||
|
||||
#if defined(HAVE_GLUT_GLUT_H)
|
||||
#include <GLUT/glut.h>
|
||||
#else
|
||||
#include <GL/glut.h>
|
||||
#ifdef FREEGLUT
|
||||
#include <GL/freeglut.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef WEBP_HAVE_QCMS
|
||||
#include <qcms.h>
|
||||
#endif
|
||||
|
||||
#include "webp/decode.h"
|
||||
#include "webp/demux.h"
|
||||
|
||||
#include "../examples/example_util.h"
|
||||
#include "../imageio/imageio_util.h"
|
||||
#include "./unicode.h"
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
// Unfortunate global variables. Gathered into a struct for comfort.
|
||||
static struct {
|
||||
int has_animation;
|
||||
int has_color_profile;
|
||||
int done;
|
||||
int decoding_error;
|
||||
int print_info;
|
||||
int only_deltas;
|
||||
int use_color_profile;
|
||||
int draw_anim_background_color;
|
||||
|
||||
int canvas_width, canvas_height;
|
||||
int loop_count;
|
||||
uint32_t bg_color;
|
||||
|
||||
const char* file_name;
|
||||
WebPData data;
|
||||
WebPDecoderConfig config;
|
||||
const WebPDecBuffer* pic;
|
||||
WebPDemuxer* dmux;
|
||||
WebPIterator curr_frame;
|
||||
WebPIterator prev_frame;
|
||||
WebPChunkIterator iccp;
|
||||
int viewport_width, viewport_height;
|
||||
} kParams;
|
||||
|
||||
static void ClearPreviousPic(void) {
|
||||
WebPFreeDecBuffer((WebPDecBuffer*)kParams.pic);
|
||||
kParams.pic = NULL;
|
||||
}
|
||||
|
||||
static void ClearParams(void) {
|
||||
ClearPreviousPic();
|
||||
WebPDataClear(&kParams.data);
|
||||
WebPDemuxReleaseIterator(&kParams.curr_frame);
|
||||
WebPDemuxReleaseIterator(&kParams.prev_frame);
|
||||
WebPDemuxReleaseChunkIterator(&kParams.iccp);
|
||||
WebPDemuxDelete(kParams.dmux);
|
||||
kParams.dmux = NULL;
|
||||
}
|
||||
|
||||
// Sets the previous frame to the dimensions of the canvas and has it dispose
|
||||
// to background to cause the canvas to be cleared.
|
||||
static void ClearPreviousFrame(void) {
|
||||
WebPIterator* const prev = &kParams.prev_frame;
|
||||
prev->width = kParams.canvas_width;
|
||||
prev->height = kParams.canvas_height;
|
||||
prev->x_offset = prev->y_offset = 0;
|
||||
prev->dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Color profile handling
|
||||
static int ApplyColorProfile(const WebPData* const profile,
|
||||
WebPDecBuffer* const rgba) {
|
||||
#ifdef WEBP_HAVE_QCMS
|
||||
int i, ok = 0;
|
||||
uint8_t* line;
|
||||
uint8_t major_revision;
|
||||
qcms_profile* input_profile = NULL;
|
||||
qcms_profile* output_profile = NULL;
|
||||
qcms_transform* transform = NULL;
|
||||
const qcms_data_type input_type = QCMS_DATA_RGBA_8;
|
||||
const qcms_data_type output_type = QCMS_DATA_RGBA_8;
|
||||
const qcms_intent intent = QCMS_INTENT_DEFAULT;
|
||||
|
||||
if (profile == NULL || rgba == NULL) return 0;
|
||||
if (profile->bytes == NULL || profile->size < 10) return 1;
|
||||
major_revision = profile->bytes[8];
|
||||
|
||||
qcms_enable_iccv4();
|
||||
input_profile = qcms_profile_from_memory(profile->bytes, profile->size);
|
||||
// qcms_profile_is_bogus() is broken with ICCv4.
|
||||
if (input_profile == NULL ||
|
||||
(major_revision < 4 && qcms_profile_is_bogus(input_profile))) {
|
||||
fprintf(stderr, "Color profile is bogus!\n");
|
||||
goto Error;
|
||||
}
|
||||
|
||||
output_profile = qcms_profile_sRGB();
|
||||
if (output_profile == NULL) {
|
||||
fprintf(stderr, "Error creating output color profile!\n");
|
||||
goto Error;
|
||||
}
|
||||
|
||||
qcms_profile_precache_output_transform(output_profile);
|
||||
transform = qcms_transform_create(input_profile, input_type,
|
||||
output_profile, output_type,
|
||||
intent);
|
||||
if (transform == NULL) {
|
||||
fprintf(stderr, "Error creating color transform!\n");
|
||||
goto Error;
|
||||
}
|
||||
|
||||
line = rgba->u.RGBA.rgba;
|
||||
for (i = 0; i < rgba->height; ++i, line += rgba->u.RGBA.stride) {
|
||||
qcms_transform_data(transform, line, line, rgba->width);
|
||||
}
|
||||
ok = 1;
|
||||
|
||||
Error:
|
||||
if (input_profile != NULL) qcms_profile_release(input_profile);
|
||||
if (output_profile != NULL) qcms_profile_release(output_profile);
|
||||
if (transform != NULL) qcms_transform_release(transform);
|
||||
return ok;
|
||||
#else
|
||||
(void)profile;
|
||||
(void)rgba;
|
||||
return 1;
|
||||
#endif // WEBP_HAVE_QCMS
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// File decoding
|
||||
|
||||
static int Decode(void) { // Fills kParams.curr_frame
|
||||
const WebPIterator* const curr = &kParams.curr_frame;
|
||||
WebPDecoderConfig* const config = &kParams.config;
|
||||
WebPDecBuffer* const output_buffer = &config->output;
|
||||
int ok = 0;
|
||||
|
||||
ClearPreviousPic();
|
||||
output_buffer->colorspace = MODE_RGBA;
|
||||
ok = (WebPDecode(curr->fragment.bytes, curr->fragment.size,
|
||||
config) == VP8_STATUS_OK);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Decoding of frame #%d failed!\n", curr->frame_num);
|
||||
} else {
|
||||
kParams.pic = output_buffer;
|
||||
if (kParams.use_color_profile) {
|
||||
ok = ApplyColorProfile(&kParams.iccp.chunk, output_buffer);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Applying color profile to frame #%d failed!\n",
|
||||
curr->frame_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void decode_callback(int what) {
|
||||
if (what == 0 && !kParams.done) {
|
||||
int duration = 0;
|
||||
if (kParams.dmux != NULL) {
|
||||
WebPIterator* const curr = &kParams.curr_frame;
|
||||
if (!WebPDemuxNextFrame(curr)) {
|
||||
WebPDemuxReleaseIterator(curr);
|
||||
if (WebPDemuxGetFrame(kParams.dmux, 1, curr)) {
|
||||
--kParams.loop_count;
|
||||
kParams.done = (kParams.loop_count == 0);
|
||||
if (kParams.done) return;
|
||||
ClearPreviousFrame();
|
||||
} else {
|
||||
kParams.decoding_error = 1;
|
||||
kParams.done = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
duration = curr->duration;
|
||||
// Behavior copied from Chrome, cf:
|
||||
// https://cs.chromium.org/chromium/src/third_party/WebKit/Source/
|
||||
// platform/graphics/DeferredImageDecoder.cpp?
|
||||
// rcl=b4c33049f096cd283f32be9a58b9a9e768227c26&l=246
|
||||
if (duration <= 10) duration = 100;
|
||||
}
|
||||
if (!Decode()) {
|
||||
kParams.decoding_error = 1;
|
||||
kParams.done = 1;
|
||||
} else {
|
||||
glutPostRedisplay();
|
||||
glutTimerFunc(duration, decode_callback, what);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Callbacks
|
||||
|
||||
static void HandleKey(unsigned char key, int pos_x, int pos_y) {
|
||||
// Note: rescaling the window or toggling some features during an animation
|
||||
// generates visual artifacts. This is not fixed because refreshing the frame
|
||||
// may require rendering the whole animation from start till current frame.
|
||||
(void)pos_x;
|
||||
(void)pos_y;
|
||||
if (key == 'q' || key == 'Q' || key == 27 /* Esc */) {
|
||||
#ifdef FREEGLUT
|
||||
glutLeaveMainLoop();
|
||||
#else
|
||||
ClearParams();
|
||||
exit(0);
|
||||
#endif
|
||||
} else if (key == 'c') {
|
||||
if (kParams.has_color_profile && !kParams.decoding_error) {
|
||||
kParams.use_color_profile = 1 - kParams.use_color_profile;
|
||||
|
||||
if (kParams.has_animation) {
|
||||
// Restart the completed animation to pickup the color profile change.
|
||||
if (kParams.done && kParams.loop_count == 0) {
|
||||
kParams.loop_count =
|
||||
(int)WebPDemuxGetI(kParams.dmux, WEBP_FF_LOOP_COUNT) + 1;
|
||||
kParams.done = 0;
|
||||
// Start the decode loop immediately.
|
||||
glutTimerFunc(0, decode_callback, 0);
|
||||
}
|
||||
} else {
|
||||
Decode();
|
||||
glutPostRedisplay();
|
||||
}
|
||||
}
|
||||
} else if (key == 'b') {
|
||||
kParams.draw_anim_background_color = 1 - kParams.draw_anim_background_color;
|
||||
if (!kParams.has_animation) ClearPreviousFrame();
|
||||
glutPostRedisplay();
|
||||
} else if (key == 'i') {
|
||||
kParams.print_info = 1 - kParams.print_info;
|
||||
if (!kParams.has_animation) ClearPreviousFrame();
|
||||
glutPostRedisplay();
|
||||
} else if (key == 'd') {
|
||||
kParams.only_deltas = 1 - kParams.only_deltas;
|
||||
glutPostRedisplay();
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleReshape(int width, int height) {
|
||||
// Note: reshape doesn't preserve aspect ratio, and might
|
||||
// be handling larger-than-screen pictures incorrectly.
|
||||
glViewport(0, 0, width, height);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
kParams.viewport_width = width;
|
||||
kParams.viewport_height = height;
|
||||
if (!kParams.has_animation) ClearPreviousFrame();
|
||||
}
|
||||
|
||||
static void PrintString(const char* const text) {
|
||||
void* const font = GLUT_BITMAP_9_BY_15;
|
||||
int i;
|
||||
for (i = 0; text[i]; ++i) {
|
||||
glutBitmapCharacter(font, text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void PrintStringW(const char* const text) {
|
||||
#if defined(_WIN32) && defined(_UNICODE)
|
||||
void* const font = GLUT_BITMAP_9_BY_15;
|
||||
const W_CHAR* const wtext = (const W_CHAR*)text;
|
||||
int i;
|
||||
for (i = 0; wtext[i]; ++i) {
|
||||
glutBitmapCharacter(font, wtext[i]);
|
||||
}
|
||||
#else
|
||||
PrintString(text);
|
||||
#endif
|
||||
}
|
||||
|
||||
static float GetColorf(uint32_t color, int shift) {
|
||||
return ((color >> shift) & 0xff) / 255.f;
|
||||
}
|
||||
|
||||
static void DrawCheckerBoard(void) {
|
||||
const int square_size = 8; // must be a power of 2
|
||||
int x, y;
|
||||
GLint viewport[4]; // x, y, width, height
|
||||
|
||||
glPushMatrix();
|
||||
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
// shift to integer coordinates with (0,0) being top-left.
|
||||
glOrtho(0, viewport[2], viewport[3], 0, -1, 1);
|
||||
for (y = 0; y < viewport[3]; y += square_size) {
|
||||
for (x = 0; x < viewport[2]; x += square_size) {
|
||||
const GLubyte color = 128 + 64 * (!((x + y) & square_size));
|
||||
glColor3ub(color, color, color);
|
||||
glRecti(x, y, x + square_size, y + square_size);
|
||||
}
|
||||
}
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
static void DrawBackground(void) {
|
||||
// Whole window cleared with clear color, checkerboard rendered on top of it.
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
DrawCheckerBoard();
|
||||
|
||||
// ANIM background color rendered (blend) on top. Default is white for still
|
||||
// images (without ANIM chunk). glClear() can't be used for that (no blend).
|
||||
if (kParams.draw_anim_background_color) {
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
glColor4f(GetColorf(kParams.bg_color, 16), // BGRA from spec
|
||||
GetColorf(kParams.bg_color, 8),
|
||||
GetColorf(kParams.bg_color, 0),
|
||||
GetColorf(kParams.bg_color, 24));
|
||||
glRecti(-1, -1, +1, +1);
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
// Draw background in a scissored rectangle.
|
||||
static void DrawBackgroundScissored(int window_x, int window_y, int frame_w,
|
||||
int frame_h) {
|
||||
// Only update the requested area, not the whole canvas.
|
||||
window_x = window_x * kParams.viewport_width / kParams.canvas_width;
|
||||
window_y = window_y * kParams.viewport_height / kParams.canvas_height;
|
||||
frame_w = frame_w * kParams.viewport_width / kParams.canvas_width;
|
||||
frame_h = frame_h * kParams.viewport_height / kParams.canvas_height;
|
||||
|
||||
// glScissor() takes window coordinates (0,0 at bottom left).
|
||||
window_y = kParams.viewport_height - window_y - frame_h;
|
||||
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(window_x, window_y, frame_w, frame_h);
|
||||
DrawBackground();
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
static void HandleDisplay(void) {
|
||||
const WebPDecBuffer* const pic = kParams.pic;
|
||||
const WebPIterator* const curr = &kParams.curr_frame;
|
||||
WebPIterator* const prev = &kParams.prev_frame;
|
||||
GLfloat xoff, yoff;
|
||||
if (pic == NULL) return;
|
||||
glPushMatrix();
|
||||
glPixelZoom((GLfloat)(+1. / kParams.canvas_width * kParams.viewport_width),
|
||||
(GLfloat)(-1. / kParams.canvas_height * kParams.viewport_height));
|
||||
xoff = (GLfloat)(2. * curr->x_offset / kParams.canvas_width);
|
||||
yoff = (GLfloat)(2. * curr->y_offset / kParams.canvas_height);
|
||||
glRasterPos2f(-1.f + xoff, 1.f - yoff);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, pic->u.RGBA.stride / 4);
|
||||
|
||||
if (kParams.only_deltas) {
|
||||
DrawBackground();
|
||||
} else {
|
||||
// The rectangle of the previous frame might be different than the current
|
||||
// frame, so we may need to DrawBackgroundScissored for both.
|
||||
if (prev->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
|
||||
// Clear the previous frame rectangle.
|
||||
DrawBackgroundScissored(prev->x_offset, prev->y_offset, prev->width,
|
||||
prev->height);
|
||||
}
|
||||
if (curr->blend_method == WEBP_MUX_NO_BLEND) {
|
||||
// We simulate no-blending behavior by first clearing the current frame
|
||||
// rectangle and then alpha-blending against it.
|
||||
DrawBackgroundScissored(curr->x_offset, curr->y_offset, curr->width,
|
||||
curr->height);
|
||||
}
|
||||
}
|
||||
|
||||
*prev = *curr;
|
||||
|
||||
glDrawPixels(pic->width, pic->height,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
(GLvoid*)pic->u.RGBA.rgba);
|
||||
if (kParams.print_info) {
|
||||
char tmp[32];
|
||||
|
||||
glColor4f(0.90f, 0.0f, 0.90f, 1.0f);
|
||||
glRasterPos2f(-0.95f, 0.90f);
|
||||
PrintStringW(kParams.file_name);
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "Dimension:%d x %d", pic->width, pic->height);
|
||||
glColor4f(0.90f, 0.0f, 0.90f, 1.0f);
|
||||
glRasterPos2f(-0.95f, 0.80f);
|
||||
PrintString(tmp);
|
||||
if (curr->x_offset != 0 || curr->y_offset != 0) {
|
||||
snprintf(tmp, sizeof(tmp), " (offset:%d,%d)",
|
||||
curr->x_offset, curr->y_offset);
|
||||
glRasterPos2f(-0.95f, 0.70f);
|
||||
PrintString(tmp);
|
||||
}
|
||||
}
|
||||
glPopMatrix();
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
glFlush();
|
||||
#else
|
||||
glutSwapBuffers();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void StartDisplay(void) {
|
||||
int width = kParams.canvas_width;
|
||||
int height = kParams.canvas_height;
|
||||
int screen_width, screen_height;
|
||||
// TODO(webp:365) GLUT_DOUBLE results in flickering / old frames to be
|
||||
// partially displayed with animated webp + alpha.
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
glutInitDisplayMode(GLUT_RGBA);
|
||||
#else
|
||||
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
|
||||
#endif
|
||||
screen_width = glutGet(GLUT_SCREEN_WIDTH);
|
||||
screen_height = glutGet(GLUT_SCREEN_HEIGHT);
|
||||
if (width > screen_width || height > screen_height) {
|
||||
if (width > screen_width) {
|
||||
height = (height * screen_width + width - 1) / width;
|
||||
width = screen_width;
|
||||
}
|
||||
if (height > screen_height) {
|
||||
width = (width * screen_height + height - 1) / height;
|
||||
height = screen_height;
|
||||
}
|
||||
}
|
||||
glutInitWindowSize(width, height);
|
||||
glutCreateWindow("WebP viewer");
|
||||
glutDisplayFunc(HandleDisplay);
|
||||
glutReshapeFunc(HandleReshape);
|
||||
glutIdleFunc(NULL);
|
||||
glutKeyboardFunc(HandleKey);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable(GL_BLEND);
|
||||
glClearColor(0, 0, 0, 0); // window will be cleared to black (no blend)
|
||||
DrawBackground();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Main
|
||||
|
||||
static void Help(void) {
|
||||
printf(
|
||||
"Usage: vwebp in_file [options]\n\n"
|
||||
"Decodes the WebP image file and visualize it using OpenGL\n"
|
||||
"Options are:\n"
|
||||
" -version ..... print version number and exit\n"
|
||||
" -noicc ....... don't use the icc profile if present\n"
|
||||
" -nofancy ..... don't use the fancy YUV420 upscaler\n"
|
||||
" -nofilter .... disable in-loop filtering\n"
|
||||
" -dither <int> dithering strength (0..100), default=50\n"
|
||||
" -noalphadither disable alpha plane dithering\n"
|
||||
" -usebgcolor .. display background color\n"
|
||||
" -mt .......... use multi-threading\n"
|
||||
" -info ........ print info\n"
|
||||
" -h ........... this help message\n"
|
||||
"\n"
|
||||
"Keyboard shortcuts:\n"
|
||||
" 'c' ................ toggle use of color profile\n"
|
||||
" 'b' ................ toggle background color display\n"
|
||||
" 'i' ................ overlay file information\n"
|
||||
" 'd' ................ disable blending & disposal (debug)\n"
|
||||
" 'q' / 'Q' / ESC .... quit\n");
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int c;
|
||||
WebPDecoderConfig* const config = &kParams.config;
|
||||
WebPIterator* const curr = &kParams.curr_frame;
|
||||
|
||||
INIT_WARGV(argc, argv);
|
||||
|
||||
if (!WebPInitDecoderConfig(config)) {
|
||||
fprintf(stderr, "Library version mismatch!\n");
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
config->options.dithering_strength = 50;
|
||||
config->options.alpha_dithering_strength = 100;
|
||||
kParams.use_color_profile = 1;
|
||||
// Background color hidden by default to see transparent areas.
|
||||
kParams.draw_anim_background_color = 0;
|
||||
|
||||
for (c = 1; c < argc; ++c) {
|
||||
int parse_error = 0;
|
||||
if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else if (!strcmp(argv[c], "-noicc")) {
|
||||
kParams.use_color_profile = 0;
|
||||
} else if (!strcmp(argv[c], "-nofancy")) {
|
||||
config->options.no_fancy_upsampling = 1;
|
||||
} else if (!strcmp(argv[c], "-nofilter")) {
|
||||
config->options.bypass_filtering = 1;
|
||||
} else if (!strcmp(argv[c], "-noalphadither")) {
|
||||
config->options.alpha_dithering_strength = 0;
|
||||
} else if (!strcmp(argv[c], "-usebgcolor")) {
|
||||
kParams.draw_anim_background_color = 1;
|
||||
} else if (!strcmp(argv[c], "-dither") && c + 1 < argc) {
|
||||
config->options.dithering_strength =
|
||||
ExUtilGetInt(argv[++c], 0, &parse_error);
|
||||
} else if (!strcmp(argv[c], "-info")) {
|
||||
kParams.print_info = 1;
|
||||
} else if (!strcmp(argv[c], "-version")) {
|
||||
const int dec_version = WebPGetDecoderVersion();
|
||||
const int dmux_version = WebPGetDemuxVersion();
|
||||
printf("WebP Decoder version: %d.%d.%d\nWebP Demux version: %d.%d.%d\n",
|
||||
(dec_version >> 16) & 0xff, (dec_version >> 8) & 0xff,
|
||||
dec_version & 0xff, (dmux_version >> 16) & 0xff,
|
||||
(dmux_version >> 8) & 0xff, dmux_version & 0xff);
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
} else if (!strcmp(argv[c], "-mt")) {
|
||||
config->options.use_threads = 1;
|
||||
} else if (!strcmp(argv[c], "--")) {
|
||||
if (c < argc - 1) kParams.file_name = (const char*)GET_WARGV(argv, ++c);
|
||||
break;
|
||||
} else if (argv[c][0] == '-') {
|
||||
printf("Unknown option '%s'\n", argv[c]);
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
} else {
|
||||
kParams.file_name = (const char*)GET_WARGV(argv, c);
|
||||
}
|
||||
|
||||
if (parse_error) {
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if (kParams.file_name == NULL) {
|
||||
printf("missing input file!!\n");
|
||||
Help();
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
}
|
||||
|
||||
if (!ImgIoUtilReadFile(kParams.file_name,
|
||||
&kParams.data.bytes, &kParams.data.size)) {
|
||||
goto Error;
|
||||
}
|
||||
|
||||
if (!WebPGetInfo(kParams.data.bytes, kParams.data.size, NULL, NULL)) {
|
||||
fprintf(stderr, "Input file doesn't appear to be WebP format.\n");
|
||||
goto Error;
|
||||
}
|
||||
|
||||
kParams.dmux = WebPDemux(&kParams.data);
|
||||
if (kParams.dmux == NULL) {
|
||||
fprintf(stderr, "Could not create demuxing object!\n");
|
||||
goto Error;
|
||||
}
|
||||
|
||||
kParams.canvas_width = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_WIDTH);
|
||||
kParams.canvas_height = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_HEIGHT);
|
||||
if (kParams.print_info) {
|
||||
printf("Canvas: %d x %d\n", kParams.canvas_width, kParams.canvas_height);
|
||||
}
|
||||
|
||||
ClearPreviousFrame();
|
||||
|
||||
memset(&kParams.iccp, 0, sizeof(kParams.iccp));
|
||||
kParams.has_color_profile =
|
||||
!!(WebPDemuxGetI(kParams.dmux, WEBP_FF_FORMAT_FLAGS) & ICCP_FLAG);
|
||||
if (kParams.has_color_profile) {
|
||||
#ifdef WEBP_HAVE_QCMS
|
||||
if (!WebPDemuxGetChunk(kParams.dmux, "ICCP", 1, &kParams.iccp)) goto Error;
|
||||
printf("VP8X: Found color profile\n");
|
||||
#else
|
||||
fprintf(stderr, "Warning: color profile present, but qcms is unavailable!\n"
|
||||
"Build libqcms from Mozilla or Chromium and define WEBP_HAVE_QCMS "
|
||||
"before building.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!WebPDemuxGetFrame(kParams.dmux, 1, curr)) goto Error;
|
||||
|
||||
kParams.has_animation = (curr->num_frames > 1);
|
||||
kParams.loop_count = (int)WebPDemuxGetI(kParams.dmux, WEBP_FF_LOOP_COUNT);
|
||||
kParams.bg_color = WebPDemuxGetI(kParams.dmux, WEBP_FF_BACKGROUND_COLOR);
|
||||
printf("VP8X: Found %d images in file (loop count = %d)\n",
|
||||
curr->num_frames, kParams.loop_count);
|
||||
|
||||
// Decode first frame
|
||||
if (!Decode()) goto Error;
|
||||
|
||||
// Position iterator to last frame. Next call to HandleDisplay will wrap over.
|
||||
// We take this into account by bumping up loop_count.
|
||||
WebPDemuxGetFrame(kParams.dmux, 0, curr);
|
||||
if (kParams.loop_count) ++kParams.loop_count;
|
||||
|
||||
#if defined(__unix__) || defined(__CYGWIN__)
|
||||
// Work around GLUT compositor bug.
|
||||
// https://bugs.launchpad.net/ubuntu/+source/freeglut/+bug/369891
|
||||
setenv("XLIB_SKIP_ARGB_VISUALS", "1", 1);
|
||||
#endif
|
||||
|
||||
// Start display (and timer)
|
||||
glutInit(&argc, argv);
|
||||
#ifdef FREEGLUT
|
||||
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);
|
||||
#endif
|
||||
StartDisplay();
|
||||
|
||||
if (kParams.has_animation) glutTimerFunc(0, decode_callback, 0);
|
||||
glutMainLoop();
|
||||
|
||||
// Should only be reached when using FREEGLUT:
|
||||
ClearParams();
|
||||
FREE_WARGV_AND_RETURN(0);
|
||||
|
||||
Error:
|
||||
ClearParams();
|
||||
FREE_WARGV_AND_RETURN(-1);
|
||||
}
|
||||
|
||||
#else // !WEBP_HAVE_GL
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
fprintf(stderr, "OpenGL support not enabled in %s.\n", argv[0]);
|
||||
(void)argc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
1186
examples/webpinfo.c
Normal file
1186
examples/webpinfo.c
Normal file
File diff suppressed because it is too large
Load Diff
1234
examples/webpmux.c
Normal file
1234
examples/webpmux.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user