mirror of
https://github.com/webmproject/libwebp.git
synced 2024-11-20 04:18:26 +01:00
migrate anim_diff tool from C++ to C89
+ jenkins fixes for native config (library order) + add a missing -lm + replace log10 by log, just in case + partially reverted configure.ac to remove the C++ part Change-Id: Iee099c544451b23c6cfaca53d5a95d2d332e066e
This commit is contained in:
parent
024324273e
commit
96201e50ea
11
Makefile.vc
11
Makefile.vc
@ -325,6 +325,7 @@ gif2webp: $(DIRBIN)\gif2webp.exe
|
|||||||
anim_diff: $(DIRBIN)\anim_diff.exe
|
anim_diff: $(DIRBIN)\anim_diff.exe
|
||||||
|
|
||||||
$(DIRBIN)\anim_diff.exe: $(DIROBJ)\examples\anim_diff.obj $(EX_ANIM_UTIL_OBJS)
|
$(DIRBIN)\anim_diff.exe: $(DIROBJ)\examples\anim_diff.obj $(EX_ANIM_UTIL_OBJS)
|
||||||
|
$(DIRBIN)\anim_diff.exe: $(EX_UTIL_OBJS)
|
||||||
$(DIRBIN)\anim_diff.exe: $(EX_GIF_DEC_OBJS) $(LIBWEBPDEMUX) $(LIBWEBP)
|
$(DIRBIN)\anim_diff.exe: $(EX_GIF_DEC_OBJS) $(LIBWEBPDEMUX) $(LIBWEBP)
|
||||||
$(DIRBIN)\cwebp.exe: $(DIROBJ)\examples\cwebp.obj $(EX_FORMAT_DEC_OBJS)
|
$(DIRBIN)\cwebp.exe: $(DIROBJ)\examples\cwebp.obj $(EX_FORMAT_DEC_OBJS)
|
||||||
$(DIRBIN)\dwebp.exe: $(DIROBJ)\examples\dwebp.obj
|
$(DIRBIN)\dwebp.exe: $(DIROBJ)\examples\dwebp.obj
|
||||||
@ -382,18 +383,18 @@ $(DIROBJ)\$(DLLINC):
|
|||||||
@echo #define WEBP_EXTERN(type) __declspec(dllexport) type >> $@
|
@echo #define WEBP_EXTERN(type) __declspec(dllexport) type >> $@
|
||||||
@echo #endif /* WEBP_DLL_H_ */ >> $@
|
@echo #endif /* WEBP_DLL_H_ */ >> $@
|
||||||
|
|
||||||
.SUFFIXES: .c .cc .obj .res .exe
|
.SUFFIXES: .c .obj .res .exe
|
||||||
# File-specific flag builds. Note batch rules take precedence over wildcards,
|
# File-specific flag builds. Note batch rules take precedence over wildcards,
|
||||||
# so for now name each file individually.
|
# so for now name each file individually.
|
||||||
$(DIROBJ)\dsp\enc_avx2.obj: src\dsp\enc_avx2.c
|
$(DIROBJ)\dsp\enc_avx2.obj: src\dsp\enc_avx2.c
|
||||||
$(CC) $(CFLAGS) $(AVX2_FLAGS) /Fd$(LIBWEBP_PDBNAME) /Fo$(DIROBJ)\dsp\ \
|
$(CC) $(CFLAGS) $(AVX2_FLAGS) /Fd$(LIBWEBP_PDBNAME) /Fo$(DIROBJ)\dsp\ \
|
||||||
src\dsp\$(@B).c
|
src\dsp\$(@B).c
|
||||||
$(DIROBJ)\examples\anim_diff.obj: examples\anim_diff.cc
|
$(DIROBJ)\examples\anim_diff.obj: examples\anim_diff.c
|
||||||
$(CC) $(CFLAGS) /DWEBP_HAVE_GIF /Fd$(LIBWEBP_PDBNAME) \
|
$(CC) $(CFLAGS) /DWEBP_HAVE_GIF /Fd$(LIBWEBP_PDBNAME) \
|
||||||
/Fo$(DIROBJ)\examples\ examples\$(@B).cc
|
/Fo$(DIROBJ)\examples\ examples\$(@B).c
|
||||||
$(DIROBJ)\examples\anim_util.obj: examples\anim_util.cc
|
$(DIROBJ)\examples\anim_util.obj: examples\anim_util.c
|
||||||
$(CC) $(CFLAGS) /DWEBP_HAVE_GIF /Fd$(LIBWEBP_PDBNAME) \
|
$(CC) $(CFLAGS) /DWEBP_HAVE_GIF /Fd$(LIBWEBP_PDBNAME) \
|
||||||
/Fo$(DIROBJ)\examples\ examples\$(@B).cc
|
/Fo$(DIROBJ)\examples\ examples\$(@B).c
|
||||||
$(DIROBJ)\examples\gif2webp.obj: examples\gif2webp.c
|
$(DIROBJ)\examples\gif2webp.obj: examples\gif2webp.c
|
||||||
$(CC) $(CFLAGS) /DWEBP_HAVE_GIF /Fd$(LIBWEBP_PDBNAME) \
|
$(CC) $(CFLAGS) /DWEBP_HAVE_GIF /Fd$(LIBWEBP_PDBNAME) \
|
||||||
/Fo$(DIROBJ)\examples\ examples\$(@B).c
|
/Fo$(DIROBJ)\examples\ examples\$(@B).c
|
||||||
|
86
configure.ac
86
configure.ac
@ -11,7 +11,6 @@ m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
|
|||||||
|
|
||||||
AC_PROG_LIBTOOL
|
AC_PROG_LIBTOOL
|
||||||
AM_PROG_CC_C_O
|
AM_PROG_CC_C_O
|
||||||
AC_PROG_CXX
|
|
||||||
|
|
||||||
dnl === Enable less verbose output when building.
|
dnl === Enable less verbose output when building.
|
||||||
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
||||||
@ -37,44 +36,34 @@ AC_ARG_WITH([pkgconfigdir], AS_HELP_STRING([--with-pkgconfigdir=DIR],
|
|||||||
[pkgconfigdir="$withval"], [pkgconfigdir='${libdir}/pkgconfig'])
|
[pkgconfigdir="$withval"], [pkgconfigdir='${libdir}/pkgconfig'])
|
||||||
AC_SUBST([pkgconfigdir])
|
AC_SUBST([pkgconfigdir])
|
||||||
|
|
||||||
dnl === TEST_AND_ADD_FLAGS(var, compiler, flag)
|
dnl === TEST_AND_ADD_CFLAGS(var, flag)
|
||||||
dnl === Checks whether $CC (or $CXX if compiler is 'CXX') supports 'flag' and
|
dnl === Checks whether $CC supports 'flag' and adds it to 'var'
|
||||||
dnl === adds it to 'var' on success.
|
dnl === on success.
|
||||||
AC_DEFUN([TEST_AND_ADD_FLAGS],
|
AC_DEFUN([TEST_AND_ADD_CFLAGS],
|
||||||
[AS_IF([test "$2" = "CXX"],
|
[SAVED_CFLAGS="$CFLAGS"
|
||||||
[SAVED_FLAGS="$CXXFLAGS"
|
CFLAGS="-Werror $2"
|
||||||
CXXFLAGS="-Werror $3"
|
AC_MSG_CHECKING([whether $CC supports $2])
|
||||||
AC_MSG_CHECKING([whether $CXX supports $3])
|
|
||||||
AC_LANG_PUSH([C++])],
|
|
||||||
[SAVED_FLAGS="$CFLAGS"
|
|
||||||
CFLAGS="-Werror $3"
|
|
||||||
AC_MSG_CHECKING([whether $CC supports $3])])
|
|
||||||
dnl Note AC_LANG_PROGRAM([]) uses an old-style main definition.
|
dnl Note AC_LANG_PROGRAM([]) uses an old-style main definition.
|
||||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([int main(void) { return 0; }])],
|
AC_COMPILE_IFELSE([AC_LANG_SOURCE([int main(void) { return 0; }])],
|
||||||
[AC_MSG_RESULT([yes])]
|
[AC_MSG_RESULT([yes])]
|
||||||
dnl Simply append the variable avoiding a
|
dnl Simply append the variable avoiding a
|
||||||
dnl compatibility ifdef for AS_VAR_APPEND as this
|
dnl compatibility ifdef for AS_VAR_APPEND as this
|
||||||
dnl variable shouldn't grow all that large.
|
dnl variable shouldn't grow all that large.
|
||||||
[$1="${$1} $3"],
|
[$1="${$1} $2"],
|
||||||
[AC_MSG_RESULT([no])])
|
[AC_MSG_RESULT([no])])
|
||||||
AS_IF([test "$2" = "CXX"],
|
CFLAGS="$SAVED_CFLAGS"])
|
||||||
[AC_LANG_POP([C++])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wall])
|
||||||
CXXFLAGS="$SAVED_FLAGS"],
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wdeclaration-after-statement])
|
||||||
[CFLAGS="$SAVED_FLAGS"])])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wextra])
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wall])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wformat-nonliteral])
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wdeclaration-after-statement])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wformat-security])
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wextra])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wmissing-declarations])
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wformat-nonliteral])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wmissing-prototypes])
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wformat-security])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wold-style-definition])
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wmissing-declarations])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wshadow])
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wmissing-prototypes])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wunused-but-set-variable])
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wold-style-definition])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wunused])
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wshadow])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-Wvla])
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wshorten-64-to-32])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wunreachable-code])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wunused-but-set-variable])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wunused])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-Wvla])
|
|
||||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62040
|
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62040
|
||||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61622
|
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61622
|
||||||
AS_IF([test "$GCC" = "yes" ], [
|
AS_IF([test "$GCC" = "yes" ], [
|
||||||
@ -87,7 +76,7 @@ AS_IF([test "$GCC" = "yes" ], [
|
|||||||
esac
|
esac
|
||||||
esac
|
esac
|
||||||
AS_IF([test "$gcc_wht_bug" = "yes"], [
|
AS_IF([test "$gcc_wht_bug" = "yes"], [
|
||||||
TEST_AND_ADD_FLAGS([AM_CFLAGS], [C], [-frename-registers])])])
|
TEST_AND_ADD_CFLAGS([AM_CFLAGS], [-frename-registers])])])
|
||||||
AC_SUBST([AM_CFLAGS])
|
AC_SUBST([AM_CFLAGS])
|
||||||
|
|
||||||
dnl === Check for machine specific flags
|
dnl === Check for machine specific flags
|
||||||
@ -98,7 +87,7 @@ AC_ARG_ENABLE([avx2],
|
|||||||
|
|
||||||
AS_IF([test "x$enable_avx2" != "xno" -a "x$enable_sse4_1" != "xno" \
|
AS_IF([test "x$enable_avx2" != "xno" -a "x$enable_sse4_1" != "xno" \
|
||||||
-a "x$enable_sse2" != "xno"], [
|
-a "x$enable_sse2" != "xno"], [
|
||||||
TEST_AND_ADD_FLAGS([AVX2_FLAGS], [C], [-mavx2])
|
TEST_AND_ADD_CFLAGS([AVX2_FLAGS], [-mavx2])
|
||||||
AS_IF([test -n "$AVX2_FLAGS"], [
|
AS_IF([test -n "$AVX2_FLAGS"], [
|
||||||
SAVED_CFLAGS=$CFLAGS
|
SAVED_CFLAGS=$CFLAGS
|
||||||
CFLAGS="$CFLAGS $AVX2_FLAGS"
|
CFLAGS="$CFLAGS $AVX2_FLAGS"
|
||||||
@ -121,7 +110,7 @@ AC_ARG_ENABLE([sse4.1],
|
|||||||
@<:@default=auto@:>@]))
|
@<:@default=auto@:>@]))
|
||||||
|
|
||||||
AS_IF([test "x$enable_sse4_1" != "xno" -a "x$enable_sse2" != "xno"], [
|
AS_IF([test "x$enable_sse4_1" != "xno" -a "x$enable_sse2" != "xno"], [
|
||||||
TEST_AND_ADD_FLAGS([SSE41_FLAGS], [C], [-msse4.1])
|
TEST_AND_ADD_CFLAGS([SSE41_FLAGS], [-msse4.1])
|
||||||
AS_IF([test -n "$SSE41_FLAGS"], [
|
AS_IF([test -n "$SSE41_FLAGS"], [
|
||||||
SAVED_CFLAGS=$CFLAGS
|
SAVED_CFLAGS=$CFLAGS
|
||||||
CFLAGS="$CFLAGS $SSE41_FLAGS"
|
CFLAGS="$CFLAGS $SSE41_FLAGS"
|
||||||
@ -138,7 +127,7 @@ AC_ARG_ENABLE([sse2],
|
|||||||
@<:@default=auto@:>@]))
|
@<:@default=auto@:>@]))
|
||||||
|
|
||||||
AS_IF([test "x$enable_sse2" != "xno"], [
|
AS_IF([test "x$enable_sse2" != "xno"], [
|
||||||
TEST_AND_ADD_FLAGS([SSE2_FLAGS], [C], [-msse2])
|
TEST_AND_ADD_CFLAGS([SSE2_FLAGS], [-msse2])
|
||||||
AS_IF([test -n "$SSE2_FLAGS"], [
|
AS_IF([test -n "$SSE2_FLAGS"], [
|
||||||
SAVED_CFLAGS=$CFLAGS
|
SAVED_CFLAGS=$CFLAGS
|
||||||
CFLAGS="$CFLAGS $SSE2_FLAGS"
|
CFLAGS="$CFLAGS $SSE2_FLAGS"
|
||||||
@ -449,30 +438,7 @@ AS_IF([test "x$enable_gif" != "xno"], [
|
|||||||
|
|
||||||
if test "$gif_support" = "yes" -a \
|
if test "$gif_support" = "yes" -a \
|
||||||
"$enable_libwebpdemux" = "yes"; then
|
"$enable_libwebpdemux" = "yes"; then
|
||||||
dnl === Enable C++ compiler for .cc sources, if available.
|
build_animdiff=yes
|
||||||
AC_LANG_PUSH([C++])
|
|
||||||
AC_LINK_IFELSE(
|
|
||||||
[AC_LANG_PROGRAM([#include <iostream>],
|
|
||||||
[std::cout << "a";])],
|
|
||||||
[cxx_support=yes],
|
|
||||||
[AC_MSG_WARN([C++ compiler absent: compilation of C++ sources disabled.])
|
|
||||||
cxx_support=no])
|
|
||||||
AC_LANG_POP([C++])
|
|
||||||
if test "$cxx_support" = "yes"; then
|
|
||||||
build_animdiff=yes
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wall])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wextra])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wformat-nonliteral])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wformat-security])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wmissing-declarations])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wshadow])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wshorten-64-to-32])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wunreachable-code])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wunused-but-set-variable])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wunused])
|
|
||||||
TEST_AND_ADD_FLAGS([AM_CXXFLAGS], [CXX], [-Wvla])
|
|
||||||
AC_SUBST([AM_CXXFLAGS])
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test "$gif_support" = "yes" -a \
|
if test "$gif_support" = "yes" -a \
|
||||||
|
@ -20,10 +20,11 @@ if BUILD_ANIMDIFF
|
|||||||
noinst_PROGRAMS = anim_diff
|
noinst_PROGRAMS = anim_diff
|
||||||
endif
|
endif
|
||||||
|
|
||||||
anim_diff_SOURCES = anim_diff.cc anim_util.cc anim_util.h
|
anim_diff_SOURCES = anim_diff.c anim_util.c anim_util.h
|
||||||
anim_diff_CPPFLAGS = $(AM_CPPFLAGS) $(GIF_INCLUDES)
|
anim_diff_CPPFLAGS = $(AM_CPPFLAGS) $(GIF_INCLUDES)
|
||||||
anim_diff_LDADD = ../src/demux/libwebpdemux.la
|
anim_diff_LDADD = ../src/demux/libwebpdemux.la
|
||||||
anim_diff_LDADD += $(GIF_LIBS)
|
anim_diff_LDADD += libexampleutil.la
|
||||||
|
anim_diff_LDADD += $(GIF_LIBS) -lm
|
||||||
|
|
||||||
dwebp_SOURCES = dwebp.c stopwatch.h
|
dwebp_SOURCES = dwebp.c stopwatch.h
|
||||||
dwebp_CPPFLAGS = $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE)
|
dwebp_CPPFLAGS = $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE)
|
||||||
|
@ -18,129 +18,136 @@
|
|||||||
#include <stdlib.h> // for 'strtod'.
|
#include <stdlib.h> // for 'strtod'.
|
||||||
#include <string.h> // for 'strcmp'.
|
#include <string.h> // for 'strcmp'.
|
||||||
|
|
||||||
#include <iostream> // for 'cout'.
|
|
||||||
#include <sstream> // for 'ostringstream'.
|
|
||||||
|
|
||||||
#include "./anim_util.h"
|
#include "./anim_util.h"
|
||||||
|
|
||||||
namespace {
|
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||||
|
#define snprintf _snprintf
|
||||||
|
#endif
|
||||||
|
|
||||||
// Return true if 'a + b' will overflow.
|
// Returns true if 'a + b' will overflow.
|
||||||
bool AdditionWillOverflow(int a, int b) {
|
static int AdditionWillOverflow(int a, int b) {
|
||||||
return (b > 0) && (a > INT_MAX - b);
|
return (b > 0) && (a > INT_MAX - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimize number of frames by combining successive frames that have exact same
|
// Minimize number of frames by combining successive frames that have exact same
|
||||||
// ARGB data into a single longer duration frame.
|
// ARGB data into a single longer duration frame.
|
||||||
void MinimizeAnimationFrames(AnimatedImage* const img) {
|
static void MinimizeAnimationFrames(AnimatedImage* const img) {
|
||||||
for (size_t i = 1; i < img->frames.size(); ++i) {
|
uint32_t i;
|
||||||
|
for (i = 1; i < img->num_frames; ++i) {
|
||||||
DecodedFrame* const frame1 = &img->frames[i - 1];
|
DecodedFrame* const frame1 = &img->frames[i - 1];
|
||||||
DecodedFrame* const frame2 = &img->frames[i];
|
DecodedFrame* const frame2 = &img->frames[i];
|
||||||
|
const uint8_t* const rgba1 = frame1->rgba;
|
||||||
|
const uint8_t* const rgba2 = frame2->rgba;
|
||||||
// If merging frames will result in integer overflow for 'duration',
|
// If merging frames will result in integer overflow for 'duration',
|
||||||
// skip merging.
|
// skip merging.
|
||||||
if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
|
if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
|
||||||
const uint8_t* rgba1 = frame1->rgba.data();
|
|
||||||
const uint8_t* rgba2 = frame2->rgba.data();
|
|
||||||
if (!memcmp(rgba1, rgba2, img->canvas_width * 4 * img->canvas_height)) {
|
if (!memcmp(rgba1, rgba2, img->canvas_width * 4 * img->canvas_height)) {
|
||||||
// Merge 'i+1'th frame into 'i'th frame.
|
// Merge 'i+1'th frame into 'i'th frame.
|
||||||
frame1->duration += frame2->duration;
|
frame1->duration += frame2->duration;
|
||||||
img->frames.erase(img->frames.begin() + i);
|
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;
|
--i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
static int CompareValues(uint32_t a, uint32_t b, const char* output_str) {
|
||||||
bool CompareValues(T a, T b, const std::string& output_str) {
|
|
||||||
if (a != b) {
|
if (a != b) {
|
||||||
std::cout << output_str << ": " << a << " vs " << b << std::endl;
|
fprintf(stderr, "%s: %d vs %d\n", output_str, a, b);
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
return true;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: As long as frame durations and reconstructed frames are identical, it
|
// Note: As long as frame durations and reconstructed frames are identical, it
|
||||||
// is OK for other aspects like offsets, dispose/blend method to vary.
|
// is OK for other aspects like offsets, dispose/blend method to vary.
|
||||||
bool CompareAnimatedImagePair(const AnimatedImage& img1,
|
static int CompareAnimatedImagePair(const AnimatedImage* const img1,
|
||||||
const AnimatedImage& img2,
|
const AnimatedImage* const img2,
|
||||||
bool premultiply,
|
int premultiply,
|
||||||
double min_psnr) {
|
double min_psnr) {
|
||||||
bool ok = true;
|
int ok = 1;
|
||||||
ok = CompareValues(img1.canvas_width, img2.canvas_width,
|
const int is_multi_frame_image = (img1->num_frames > 1);
|
||||||
"Canvas width mismatch") && ok;
|
uint32_t i;
|
||||||
ok = CompareValues(img1.canvas_height, img2.canvas_height,
|
|
||||||
"Canvas height mismatch") && ok;
|
ok = CompareValues(img1->canvas_width, img2->canvas_width,
|
||||||
ok = CompareValues(img1.frames.size(), img2.frames.size(),
|
"Canvas width mismatch") && ok;
|
||||||
"Frame count mismatch") && ok;
|
ok = CompareValues(img1->canvas_height, img2->canvas_height,
|
||||||
if (!ok) return false; // These are fatal failures, can't proceed.
|
"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.
|
||||||
|
|
||||||
const bool is_multi_frame_image = (img1.frames.size() > 1);
|
|
||||||
if (is_multi_frame_image) { // Checks relevant for multi-frame images only.
|
if (is_multi_frame_image) { // Checks relevant for multi-frame images only.
|
||||||
ok = CompareValues(img1.loop_count, img2.loop_count,
|
ok = CompareValues(img1->loop_count, img2->loop_count,
|
||||||
"Loop count mismatch") && ok;
|
"Loop count mismatch") && ok;
|
||||||
ok = CompareValues(img1.bgcolor, img2.bgcolor,
|
ok = CompareValues(img1->bgcolor, img2->bgcolor,
|
||||||
"Background color mismatch") && ok;
|
"Background color mismatch") && ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < img1.frames.size(); ++i) {
|
for (i = 0; i < img1->num_frames; ++i) {
|
||||||
if (is_multi_frame_image) { // Check relevant for multi-frame images only.
|
|
||||||
std::ostringstream error_str;
|
|
||||||
error_str << "Frame #" << i << ", duration mismatch";
|
|
||||||
ok = CompareValues(img1.frames[i].duration, img2.frames[i].duration,
|
|
||||||
error_str.str()) && ok;
|
|
||||||
}
|
|
||||||
// Pixel-by-pixel comparison.
|
// Pixel-by-pixel comparison.
|
||||||
const uint8_t* rgba1 = img1.frames[i].rgba.data();
|
const uint8_t* const rgba1 = img1->frames[i].rgba;
|
||||||
const uint8_t* rgba2 = img2.frames[i].rgba.data();
|
const uint8_t* const rgba2 = img2->frames[i].rgba;
|
||||||
int max_diff;
|
int max_diff;
|
||||||
double psnr;
|
double psnr;
|
||||||
GetDiffAndPSNR(rgba1, rgba2, img1.canvas_width, img1.canvas_height,
|
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);
|
premultiply, &max_diff, &psnr);
|
||||||
if (min_psnr > 0.) {
|
if (min_psnr > 0.) {
|
||||||
if (psnr < min_psnr) {
|
if (psnr < min_psnr) {
|
||||||
fprintf(stderr, "Frame #%zu, psnr = %.2lf (min_psnr = %f)\n", i,
|
fprintf(stderr, "Frame #%d, psnr = %.2lf (min_psnr = %f)\n", i,
|
||||||
psnr, min_psnr);
|
psnr, min_psnr);
|
||||||
ok = false;
|
ok = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (max_diff != 0) {
|
if (max_diff != 0) {
|
||||||
fprintf(stderr, "Frame #%zu, max pixel diff: %d\n", i, max_diff);
|
fprintf(stderr, "Frame #%d, max pixel diff: %d\n", i, max_diff);
|
||||||
ok = false;
|
ok = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Help() {
|
static void Help(void) {
|
||||||
printf("\nUsage: anim_diff <image1> <image2> [-dump_frames <folder>] "
|
printf("\nUsage: anim_diff <image1> <image2> [-dump_frames <folder>] "
|
||||||
"[-min_psnr <float>][-raw_comparison]\n");
|
"[-min_psnr <float>][-raw_comparison]\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int main(int argc, const char* argv[]) {
|
int main(int argc, const char* argv[]) {
|
||||||
bool dump_frames = false;
|
int return_code = -1;
|
||||||
|
int dump_frames = 0;
|
||||||
const char* dump_folder = NULL;
|
const char* dump_folder = NULL;
|
||||||
double min_psnr = 0.;
|
double min_psnr = 0.;
|
||||||
bool got_input1 = false;
|
int got_input1 = 0;
|
||||||
bool got_input2 = false;
|
int got_input2 = 0;
|
||||||
bool premultiply = true;
|
int premultiply = 1;
|
||||||
const char* files[2];
|
int i, c;
|
||||||
|
const char* files[2] = { NULL, NULL };
|
||||||
|
AnimatedImage images[2];
|
||||||
|
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
Help();
|
Help();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int c = 1; c < argc; ++c) {
|
for (c = 1; c < argc; ++c) {
|
||||||
bool parse_error = false;
|
int parse_error = 0;
|
||||||
if (!strcmp(argv[c], "-dump_frames")) {
|
if (!strcmp(argv[c], "-dump_frames")) {
|
||||||
if (c < argc - 1) {
|
if (c < argc - 1) {
|
||||||
dump_frames = true;
|
dump_frames = 1;
|
||||||
dump_folder = argv[++c];
|
dump_folder = argv[++c];
|
||||||
} else {
|
} else {
|
||||||
parse_error = true;
|
parse_error = 1;
|
||||||
}
|
}
|
||||||
} else if (!strcmp(argv[c], "-min_psnr")) {
|
} else if (!strcmp(argv[c], "-min_psnr")) {
|
||||||
if (c < argc - 1) {
|
if (c < argc - 1) {
|
||||||
@ -148,24 +155,24 @@ int main(int argc, const char* argv[]) {
|
|||||||
char* end = NULL;
|
char* end = NULL;
|
||||||
const double d = strtod(v, &end);
|
const double d = strtod(v, &end);
|
||||||
if (end == v) {
|
if (end == v) {
|
||||||
parse_error = true;
|
parse_error = 1;
|
||||||
fprintf(stderr, "Error! '%s' is not a floating point number.\n", v);
|
fprintf(stderr, "Error! '%s' is not a floating point number.\n", v);
|
||||||
}
|
}
|
||||||
min_psnr = d;
|
min_psnr = d;
|
||||||
} else {
|
} else {
|
||||||
parse_error = true;
|
parse_error = 1;
|
||||||
}
|
}
|
||||||
} else if (!strcmp(argv[c], "-raw_comparison")) {
|
} else if (!strcmp(argv[c], "-raw_comparison")) {
|
||||||
premultiply = false;
|
premultiply = 0;
|
||||||
} else {
|
} else {
|
||||||
if (!got_input1) {
|
if (!got_input1) {
|
||||||
files[0] = argv[c];
|
files[0] = argv[c];
|
||||||
got_input1 = true;
|
got_input1 = 1;
|
||||||
} else if (!got_input2) {
|
} else if (!got_input2) {
|
||||||
files[1] = argv[c];
|
files[1] = argv[c];
|
||||||
got_input2 = true;
|
got_input2 = 1;
|
||||||
} else {
|
} else {
|
||||||
parse_error = true;
|
parse_error = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (parse_error) {
|
if (parse_error) {
|
||||||
@ -182,22 +189,28 @@ int main(int argc, const char* argv[]) {
|
|||||||
printf("Dumping decoded frames in: %s\n", dump_folder);
|
printf("Dumping decoded frames in: %s\n", dump_folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedImage images[2];
|
memset(images, 0, sizeof(images));
|
||||||
for (int i = 0; i < 2; ++i) {
|
for (i = 0; i < 2; ++i) {
|
||||||
printf("Decoding file: %s\n", files[i]);
|
printf("Decoding file: %s\n", files[i]);
|
||||||
if (!ReadAnimatedImage(files[i], &images[i], dump_frames, dump_folder)) {
|
if (!ReadAnimatedImage(files[i], &images[i], dump_frames, dump_folder)) {
|
||||||
fprintf(stderr, "Error decoding file: %s\n Aborting.\n", files[i]);
|
fprintf(stderr, "Error decoding file: %s\n Aborting.\n", files[i]);
|
||||||
return -2;
|
return_code = -2;
|
||||||
|
goto End;
|
||||||
|
} else {
|
||||||
|
MinimizeAnimationFrames(&images[i]);
|
||||||
}
|
}
|
||||||
MinimizeAnimationFrames(&images[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CompareAnimatedImagePair(images[0], images[1],
|
if (!CompareAnimatedImagePair(&images[0], &images[1],
|
||||||
premultiply, min_psnr)) {
|
premultiply, min_psnr)) {
|
||||||
fprintf(stderr, "\nFiles %s and %s differ.\n", files[0], files[1]);
|
fprintf(stderr, "\nFiles %s and %s differ.\n", files[0], files[1]);
|
||||||
return -3;
|
return_code = -3;
|
||||||
|
} else {
|
||||||
|
printf("\nFiles %s and %s are identical.\n", files[0], files[1]);
|
||||||
|
return_code = 0;
|
||||||
}
|
}
|
||||||
|
End:
|
||||||
printf("\nFiles %s and %s are identical.\n", files[0], files[1]);
|
ClearAnimatedImage(&images[0]);
|
||||||
return 0;
|
ClearAnimatedImage(&images[1]);
|
||||||
|
return return_code;
|
||||||
}
|
}
|
@ -16,20 +16,17 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream> // for 'ostringstream'.
|
|
||||||
|
|
||||||
#ifdef WEBP_HAVE_GIF
|
#ifdef WEBP_HAVE_GIF
|
||||||
#include <gif_lib.h>
|
#include <gif_lib.h>
|
||||||
#endif
|
#endif
|
||||||
#include "webp/format_constants.h"
|
#include "webp/format_constants.h"
|
||||||
#include "webp/decode.h"
|
#include "webp/decode.h"
|
||||||
#include "webp/demux.h"
|
#include "webp/demux.h"
|
||||||
|
#include "./example_util.h"
|
||||||
|
|
||||||
using std::ifstream;
|
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||||
using std::ios;
|
#define snprintf _snprintf
|
||||||
using std::ofstream;
|
#endif
|
||||||
using std::ostringstream;
|
|
||||||
|
|
||||||
static const int kNumChannels = 4;
|
static const int kNumChannels = 4;
|
||||||
|
|
||||||
@ -37,17 +34,43 @@ static const int kNumChannels = 4;
|
|||||||
// Common utilities.
|
// Common utilities.
|
||||||
|
|
||||||
// Returns true if the frame covers the full canvas.
|
// Returns true if the frame covers the full canvas.
|
||||||
static bool IsFullFrame(int width, int height,
|
static int IsFullFrame(int width, int height,
|
||||||
int canvas_width, int canvas_height) {
|
int canvas_width, int canvas_height) {
|
||||||
return (width == canvas_width && height == canvas_height);
|
return (width == canvas_width && height == canvas_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void AllocateFrames(AnimatedImage* const image, uint32_t frame_count) {
|
static int AllocateFrames(AnimatedImage* const image, uint32_t num_frames) {
|
||||||
image->frames.resize(frame_count);
|
uint32_t i;
|
||||||
for (size_t i = 0; i < image->frames.size(); ++i) {
|
const size_t rgba_size =
|
||||||
const size_t rgba_size =
|
image->canvas_width * kNumChannels * image->canvas_height;
|
||||||
image->canvas_width * kNumChannels * image->canvas_height;
|
uint8_t* const mem = (uint8_t*)malloc(num_frames * rgba_size * sizeof(*mem));
|
||||||
image->frames[i].rgba.resize(rgba_size);
|
DecodedFrame* const frames =
|
||||||
|
(DecodedFrame*)malloc(num_frames * sizeof(*frames));
|
||||||
|
|
||||||
|
if (mem == NULL || frames == NULL) {
|
||||||
|
free(mem);
|
||||||
|
free(frames);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
free(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) {
|
||||||
|
free(image->raw_mem);
|
||||||
|
free(image->frames);
|
||||||
|
image->num_frames = 0;
|
||||||
|
image->frames = NULL;
|
||||||
|
image->raw_mem = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,9 +83,10 @@ static void ZeroFillCanvas(uint8_t* rgba,
|
|||||||
// Clear given frame rectangle to transparent.
|
// Clear given frame rectangle to transparent.
|
||||||
static void ZeroFillFrameRect(uint8_t* rgba, int rgba_stride, int x_offset,
|
static void ZeroFillFrameRect(uint8_t* rgba, int rgba_stride, int x_offset,
|
||||||
int y_offset, int width, int height) {
|
int y_offset, int width, int height) {
|
||||||
|
int j;
|
||||||
assert(width * kNumChannels <= rgba_stride);
|
assert(width * kNumChannels <= rgba_stride);
|
||||||
rgba += y_offset * rgba_stride + x_offset * kNumChannels;
|
rgba += y_offset * rgba_stride + x_offset * kNumChannels;
|
||||||
for (int j = 0; j < height; ++j) {
|
for (j = 0; j < height; ++j) {
|
||||||
memset(rgba, 0, width * kNumChannels);
|
memset(rgba, 0, width * kNumChannels);
|
||||||
rgba += rgba_stride;
|
rgba += rgba_stride;
|
||||||
}
|
}
|
||||||
@ -79,12 +103,13 @@ static void CopyCanvas(const uint8_t* src, uint8_t* dst,
|
|||||||
static void CopyFrameRectangle(const uint8_t* src, uint8_t* dst, int stride,
|
static void CopyFrameRectangle(const uint8_t* src, uint8_t* dst, int stride,
|
||||||
int x_offset, int y_offset,
|
int x_offset, int y_offset,
|
||||||
int width, int height) {
|
int width, int height) {
|
||||||
|
int j;
|
||||||
const int width_in_bytes = width * kNumChannels;
|
const int width_in_bytes = width * kNumChannels;
|
||||||
assert(width_in_bytes <= stride);
|
|
||||||
const size_t offset = y_offset * stride + x_offset * kNumChannels;
|
const size_t offset = y_offset * stride + x_offset * kNumChannels;
|
||||||
|
assert(width_in_bytes <= stride);
|
||||||
src += offset;
|
src += offset;
|
||||||
dst += offset;
|
dst += offset;
|
||||||
for (int j = 0; j < height; ++j) {
|
for (j = 0; j < height; ++j) {
|
||||||
memcpy(dst, src, width_in_bytes);
|
memcpy(dst, src, width_in_bytes);
|
||||||
src += stride;
|
src += stride;
|
||||||
dst += stride;
|
dst += stride;
|
||||||
@ -104,71 +129,84 @@ static void CleanupTransparentPixels(uint32_t* rgba,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump frame to a PAM file.
|
// Dump frame to a PAM file. Returns true on success.
|
||||||
// Returns true on success.
|
static int DumpFrame(const char filename[], const char dump_folder[],
|
||||||
static bool DumpFrame(const char filename[], const char dump_folder[],
|
uint32_t frame_num, const uint8_t rgba[],
|
||||||
uint32_t frame_num, const uint8_t rgba[],
|
int canvas_width, int canvas_height) {
|
||||||
int canvas_width, int canvas_height) {
|
int ok = 0;
|
||||||
const std::string filename_str = filename;
|
size_t max_len;
|
||||||
const size_t slash_idx = filename_str.find_last_of("/\\");
|
int y;
|
||||||
const std::string base_name = (slash_idx != std::string::npos)
|
const char* base_name = NULL;
|
||||||
? filename_str.substr(slash_idx + 1)
|
char* file_name = NULL;
|
||||||
: filename_str;
|
FILE* f = NULL;
|
||||||
ostringstream dump_file;
|
|
||||||
dump_file << dump_folder << "/" << base_name << "_frame_" << frame_num
|
|
||||||
<< ".pam";
|
|
||||||
|
|
||||||
ofstream fout(dump_file.str().c_str(), ios::binary | ios::out);
|
base_name = strrchr(filename, '/');
|
||||||
if (!fout.good()) {
|
base_name = (base_name == NULL) ? filename : base_name + 1;
|
||||||
fprintf(stderr, "Error opening file for writing: %s\n",
|
max_len = strlen(dump_folder) + 1 + strlen(base_name)
|
||||||
dump_file.str().c_str());
|
+ strlen("_frame_") + strlen(".pam") + 8;
|
||||||
return false;
|
file_name = (char*)malloc(max_len * sizeof(*file_name));
|
||||||
|
if (file_name == NULL) goto End;
|
||||||
|
|
||||||
|
if (snprintf(file_name, max_len, "%s/%s_frame_%d.pam",
|
||||||
|
dump_folder, base_name, frame_num) < 0) {
|
||||||
|
fprintf(stderr, "Error while generating file name\n");
|
||||||
|
goto End;
|
||||||
}
|
}
|
||||||
|
|
||||||
fout << "P7\nWIDTH " << canvas_width << "\nHEIGHT " << canvas_height
|
f = fopen(file_name, "wb");
|
||||||
<< "\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n";
|
if (f == NULL) {
|
||||||
for (int y = 0; y < canvas_height; ++y) {
|
fprintf(stderr, "Error opening file for writing: %s\n", file_name);
|
||||||
fout.write(
|
ok = 0;
|
||||||
reinterpret_cast<const char*>(rgba) + y * canvas_width * kNumChannels,
|
goto End;
|
||||||
canvas_width * kNumChannels);
|
}
|
||||||
if (!fout.good()) {
|
if (fprintf(f, "P7\nWIDTH %d\nHEIGHT %d\n"
|
||||||
fprintf(stderr, "Error writing to file: %s\n", dump_file.str().c_str());
|
"DEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n",
|
||||||
return 0;
|
canvas_width, canvas_height) < 0) {
|
||||||
|
fprintf(stderr, "Write error for file %s\n", file_name);
|
||||||
|
goto End;
|
||||||
|
}
|
||||||
|
for (y = 0; y < canvas_height; ++y) {
|
||||||
|
if (fwrite((const char*)(rgba) + y * canvas_width * kNumChannels,
|
||||||
|
canvas_width * kNumChannels, 1, f) != 1) {
|
||||||
|
fprintf(stderr, "Error writing to file: %s\n", file_name);
|
||||||
|
goto End;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fout.close();
|
ok = 1;
|
||||||
return true;
|
End:
|
||||||
|
if (f != NULL) fclose(f);
|
||||||
|
free(file_name);
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// WebP Decoding.
|
// WebP Decoding.
|
||||||
|
|
||||||
// Returns true if this is a valid WebP bitstream.
|
// Returns true if this is a valid WebP bitstream.
|
||||||
static bool IsWebP(const std::string& file_str) {
|
static int IsWebP(const WebPData* const webp_data) {
|
||||||
return WebPGetInfo(reinterpret_cast<const uint8_t*>(file_str.c_str()),
|
return (WebPGetInfo(webp_data->bytes, webp_data->size, NULL, NULL) != 0);
|
||||||
file_str.length(), NULL, NULL) != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read animated WebP bitstream 'file_str' into 'AnimatedImage' struct.
|
// Read animated WebP bitstream 'file_str' into 'AnimatedImage' struct.
|
||||||
static bool ReadAnimatedWebP(const char filename[], const std::string& file_str,
|
static int ReadAnimatedWebP(const char filename[],
|
||||||
AnimatedImage* const image, bool dump_frames,
|
const WebPData* const webp_data,
|
||||||
const char dump_folder[]) {
|
AnimatedImage* const image, int dump_frames,
|
||||||
bool ok = false;
|
const char dump_folder[]) {
|
||||||
bool dump_ok = true;
|
int ok = 0;
|
||||||
|
int dump_ok = 1;
|
||||||
uint32_t frame_index = 0;
|
uint32_t frame_index = 0;
|
||||||
int prev_frame_timestamp = 0;
|
int prev_frame_timestamp = 0;
|
||||||
|
WebPAnimDecoder* dec;
|
||||||
|
WebPAnimInfo anim_info;
|
||||||
|
|
||||||
const WebPData webp_data = {
|
memset(image, 0, sizeof(*image));
|
||||||
reinterpret_cast<const uint8_t*>(file_str.data()), file_str.size()
|
|
||||||
};
|
|
||||||
|
|
||||||
WebPAnimDecoder* dec = WebPAnimDecoderNew(&webp_data);
|
dec = WebPAnimDecoderNew(webp_data);
|
||||||
if (dec == NULL) {
|
if (dec == NULL) {
|
||||||
fprintf(stderr, "Error parsing image: %s\n", filename);
|
fprintf(stderr, "Error parsing image: %s\n", filename);
|
||||||
goto End;
|
goto End;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebPAnimInfo anim_info;
|
|
||||||
if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
|
if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
|
||||||
fprintf(stderr, "Error getting global info about the animation\n");
|
fprintf(stderr, "Error getting global info about the animation\n");
|
||||||
goto End;
|
goto End;
|
||||||
@ -181,25 +219,28 @@ static bool ReadAnimatedWebP(const char filename[], const std::string& file_str,
|
|||||||
image->bgcolor = anim_info.bgcolor;
|
image->bgcolor = anim_info.bgcolor;
|
||||||
|
|
||||||
// Allocate frames.
|
// Allocate frames.
|
||||||
AllocateFrames(image, anim_info.frame_count);
|
if (!AllocateFrames(image, anim_info.frame_count)) return 0;
|
||||||
|
|
||||||
// Decode frames.
|
// Decode frames.
|
||||||
while (WebPAnimDecoderHasMoreFrames(dec)) {
|
while (WebPAnimDecoderHasMoreFrames(dec)) {
|
||||||
|
DecodedFrame* curr_frame;
|
||||||
|
uint8_t* curr_rgba;
|
||||||
uint8_t* frame_rgba;
|
uint8_t* frame_rgba;
|
||||||
int timestamp;
|
int timestamp;
|
||||||
|
|
||||||
if (!WebPAnimDecoderGetNext(dec, &frame_rgba, ×tamp)) {
|
if (!WebPAnimDecoderGetNext(dec, &frame_rgba, ×tamp)) {
|
||||||
fprintf(stderr, "Error decoding frame #%u\n", frame_index);
|
fprintf(stderr, "Error decoding frame #%u\n", frame_index);
|
||||||
goto End;
|
goto End;
|
||||||
}
|
}
|
||||||
DecodedFrame* const curr_frame = &image->frames[frame_index];
|
curr_frame = &image->frames[frame_index];
|
||||||
uint8_t* const curr_rgba = curr_frame->rgba.data();
|
curr_rgba = curr_frame->rgba;
|
||||||
curr_frame->duration = timestamp - prev_frame_timestamp;
|
curr_frame->duration = timestamp - prev_frame_timestamp;
|
||||||
curr_frame->is_key_frame = false; // Unused.
|
curr_frame->is_key_frame = 0; // Unused.
|
||||||
memcpy(curr_rgba, frame_rgba,
|
memcpy(curr_rgba, frame_rgba,
|
||||||
image->canvas_width * kNumChannels * image->canvas_height);
|
image->canvas_width * kNumChannels * image->canvas_height);
|
||||||
|
|
||||||
// Needed only because we may want to compare with GIF later.
|
// Needed only because we may want to compare with GIF later.
|
||||||
CleanupTransparentPixels(reinterpret_cast<uint32_t*>(curr_rgba),
|
CleanupTransparentPixels((uint32_t*)curr_rgba,
|
||||||
image->canvas_width, image->canvas_height);
|
image->canvas_width, image->canvas_height);
|
||||||
|
|
||||||
if (dump_frames && dump_ok) {
|
if (dump_frames && dump_ok) {
|
||||||
@ -224,12 +265,11 @@ static bool ReadAnimatedWebP(const char filename[], const std::string& file_str,
|
|||||||
// GIF Decoding.
|
// GIF Decoding.
|
||||||
|
|
||||||
// Returns true if this is a valid GIF bitstream.
|
// Returns true if this is a valid GIF bitstream.
|
||||||
static bool IsGIF(const std::string& file_str) {
|
static int IsGIF(const WebPData* const data) {
|
||||||
const char* const cstr = file_str.c_str();
|
return data->size > GIF_STAMP_LEN &&
|
||||||
return file_str.length() > GIF_STAMP_LEN &&
|
(!memcmp(GIF_STAMP, data->bytes, GIF_STAMP_LEN) ||
|
||||||
(!memcmp(GIF_STAMP, cstr, GIF_STAMP_LEN) ||
|
!memcmp(GIF87_STAMP, data->bytes, GIF_STAMP_LEN) ||
|
||||||
!memcmp(GIF87_STAMP, cstr, GIF_STAMP_LEN) ||
|
!memcmp(GIF89_STAMP, data->bytes, GIF_STAMP_LEN));
|
||||||
!memcmp(GIF89_STAMP, cstr, GIF_STAMP_LEN));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WEBP_HAVE_GIF
|
#ifdef WEBP_HAVE_GIF
|
||||||
@ -247,13 +287,13 @@ static bool IsGIF(const std::string& file_str) {
|
|||||||
#if !LOCAL_GIF_PREREQ(5, 0)
|
#if !LOCAL_GIF_PREREQ(5, 0)
|
||||||
|
|
||||||
// Added in v5.0
|
// Added in v5.0
|
||||||
typedef struct GraphicsControlBlock {
|
typedef struct {
|
||||||
int DisposalMode;
|
int DisposalMode;
|
||||||
#define DISPOSAL_UNSPECIFIED 0 // No disposal specified
|
#define DISPOSAL_UNSPECIFIED 0 // No disposal specified
|
||||||
#define DISPOSE_DO_NOT 1 // Leave image in place
|
#define DISPOSE_DO_NOT 1 // Leave image in place
|
||||||
#define DISPOSE_BACKGROUND 2 // Set area to background color
|
#define DISPOSE_BACKGROUND 2 // Set area to background color
|
||||||
#define DISPOSE_PREVIOUS 3 // Restore to previous content
|
#define DISPOSE_PREVIOUS 3 // Restore to previous content
|
||||||
bool UserInputFlag; // User confirmation required before disposal
|
int UserInputFlag; // User confirmation required before disposal
|
||||||
int DelayTime; // Pre-display delay in 0.01sec units
|
int DelayTime; // Pre-display delay in 0.01sec units
|
||||||
int TransparentColor; // Palette index for transparency, -1 if none
|
int TransparentColor; // Palette index for transparency, -1 if none
|
||||||
#define NO_TRANSPARENT_COLOR -1
|
#define NO_TRANSPARENT_COLOR -1
|
||||||
@ -269,7 +309,7 @@ static int DGifExtensionToGCB(const size_t GifExtensionLength,
|
|||||||
gcb->UserInputFlag = (GifExtension[0] & 0x02) != 0;
|
gcb->UserInputFlag = (GifExtension[0] & 0x02) != 0;
|
||||||
gcb->DelayTime = GifExtension[1] | (GifExtension[2] << 8);
|
gcb->DelayTime = GifExtension[1] | (GifExtension[2] << 8);
|
||||||
if (GifExtension[0] & 0x01) {
|
if (GifExtension[0] & 0x01) {
|
||||||
gcb->TransparentColor = static_cast<int>(GifExtension[3]);
|
gcb->TransparentColor = (int)GifExtension[3];
|
||||||
} else {
|
} else {
|
||||||
gcb->TransparentColor = NO_TRANSPARENT_COLOR;
|
gcb->TransparentColor = NO_TRANSPARENT_COLOR;
|
||||||
}
|
}
|
||||||
@ -283,7 +323,7 @@ static int DGifSavedExtensionToGCB(GifFileType* GifFile, int ImageIndex,
|
|||||||
return GIF_ERROR;
|
return GIF_ERROR;
|
||||||
}
|
}
|
||||||
gcb->DisposalMode = DISPOSAL_UNSPECIFIED;
|
gcb->DisposalMode = DISPOSAL_UNSPECIFIED;
|
||||||
gcb->UserInputFlag = false;
|
gcb->UserInputFlag = 0;
|
||||||
gcb->DelayTime = 0;
|
gcb->DelayTime = 0;
|
||||||
gcb->TransparentColor = NO_TRANSPARENT_COLOR;
|
gcb->TransparentColor = NO_TRANSPARENT_COLOR;
|
||||||
|
|
||||||
@ -291,7 +331,7 @@ static int DGifSavedExtensionToGCB(GifFileType* GifFile, int ImageIndex,
|
|||||||
ExtensionBlock* ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
|
ExtensionBlock* ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
|
||||||
if (ep->Function == GRAPHICS_EXT_FUNC_CODE) {
|
if (ep->Function == GRAPHICS_EXT_FUNC_CODE) {
|
||||||
return DGifExtensionToGCB(
|
return DGifExtensionToGCB(
|
||||||
ep->ByteCount, reinterpret_cast<const GifByteType*>(ep->Bytes), gcb);
|
ep->ByteCount, (const GifByteType*)ep->Bytes, gcb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return GIF_ERROR;
|
return GIF_ERROR;
|
||||||
@ -313,11 +353,10 @@ static void GIFDisplayError(const GifFileType* const gif, int gif_error) {
|
|||||||
// libgif 4.2.0 has retired PrintGifError() and added GifErrorString().
|
// libgif 4.2.0 has retired PrintGifError() and added GifErrorString().
|
||||||
#if LOCAL_GIF_PREREQ(4, 2)
|
#if LOCAL_GIF_PREREQ(4, 2)
|
||||||
#if LOCAL_GIF_PREREQ(5, 0)
|
#if LOCAL_GIF_PREREQ(5, 0)
|
||||||
// Static string actually, hence the const char* cast.
|
const char* error_str =
|
||||||
const char* error_str = (const char*)GifErrorString(
|
GifErrorString((gif == NULL) ? gif_error : gif->Error);
|
||||||
(gif == NULL) ? gif_error : gif->Error);
|
|
||||||
#else
|
#else
|
||||||
const char* error_str = (const char*)GifErrorString();
|
const char* error_str = GifErrorString();
|
||||||
(void)gif;
|
(void)gif;
|
||||||
#endif
|
#endif
|
||||||
if (error_str == NULL) error_str = "Unknown error";
|
if (error_str == NULL) error_str = "Unknown error";
|
||||||
@ -330,22 +369,23 @@ static void GIFDisplayError(const GifFileType* const gif, int gif_error) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsKeyFrameGIF(const GifImageDesc& prev_desc, int prev_dispose,
|
static int IsKeyFrameGIF(const GifImageDesc* prev_desc, int prev_dispose,
|
||||||
const DecodedFrame* const prev_frame,
|
const DecodedFrame* const prev_frame,
|
||||||
int canvas_width, int canvas_height) {
|
int canvas_width, int canvas_height) {
|
||||||
if (prev_frame == NULL) return true;
|
if (prev_frame == NULL) return 1;
|
||||||
if (prev_dispose == DISPOSE_BACKGROUND) {
|
if (prev_dispose == DISPOSE_BACKGROUND) {
|
||||||
if (IsFullFrame(prev_desc.Width, prev_desc.Height,
|
if (IsFullFrame(prev_desc->Width, prev_desc->Height,
|
||||||
canvas_width, canvas_height)) {
|
canvas_width, canvas_height)) {
|
||||||
return true;
|
return 1;
|
||||||
}
|
}
|
||||||
if (prev_frame->is_key_frame) return true;
|
if (prev_frame->is_key_frame) return 1;
|
||||||
}
|
}
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int GetTransparentIndexGIF(GifFileType* gif) {
|
static int GetTransparentIndexGIF(GifFileType* gif) {
|
||||||
GraphicsControlBlock first_gcb = GraphicsControlBlock();
|
GraphicsControlBlock first_gcb;
|
||||||
|
memset(&first_gcb, 0, sizeof(first_gcb));
|
||||||
DGifSavedExtensionToGCB(gif, 0, &first_gcb);
|
DGifSavedExtensionToGCB(gif, 0, &first_gcb);
|
||||||
return first_gcb.TransparentColor;
|
return first_gcb.TransparentColor;
|
||||||
}
|
}
|
||||||
@ -370,13 +410,15 @@ static uint32_t GetBackgroundColorGIF(GifFileType* gif) {
|
|||||||
|
|
||||||
// Find appropriate app extension and get loop count from the next extension.
|
// Find appropriate app extension and get loop count from the next extension.
|
||||||
static uint32_t GetLoopCountGIF(const GifFileType* const gif) {
|
static uint32_t GetLoopCountGIF(const GifFileType* const gif) {
|
||||||
for (int i = 0; i < gif->ImageCount; ++i) {
|
int i;
|
||||||
|
for (i = 0; i < gif->ImageCount; ++i) {
|
||||||
const SavedImage* const image = &gif->SavedImages[i];
|
const SavedImage* const image = &gif->SavedImages[i];
|
||||||
for (int j = 0; (j + 1) < image->ExtensionBlockCount; ++j) {
|
int j;
|
||||||
|
for (j = 0; (j + 1) < image->ExtensionBlockCount; ++j) {
|
||||||
const ExtensionBlock* const eb1 = image->ExtensionBlocks + j;
|
const ExtensionBlock* const eb1 = image->ExtensionBlocks + j;
|
||||||
const ExtensionBlock* const eb2 = image->ExtensionBlocks + j + 1;
|
const ExtensionBlock* const eb2 = image->ExtensionBlocks + j + 1;
|
||||||
const char* const signature = reinterpret_cast<const char*>(eb1->Bytes);
|
const char* const signature = (const char*)eb1->Bytes;
|
||||||
const bool signature_is_ok =
|
const int signature_is_ok =
|
||||||
(eb1->Function == APPLICATION_EXT_FUNC_CODE) &&
|
(eb1->Function == APPLICATION_EXT_FUNC_CODE) &&
|
||||||
(eb1->ByteCount == 11) &&
|
(eb1->ByteCount == 11) &&
|
||||||
(!memcmp(signature, "NETSCAPE2.0", 11) ||
|
(!memcmp(signature, "NETSCAPE2.0", 11) ||
|
||||||
@ -384,8 +426,8 @@ static uint32_t GetLoopCountGIF(const GifFileType* const gif) {
|
|||||||
if (signature_is_ok &&
|
if (signature_is_ok &&
|
||||||
eb2->Function == CONTINUE_EXT_FUNC_CODE && eb2->ByteCount >= 3 &&
|
eb2->Function == CONTINUE_EXT_FUNC_CODE && eb2->ByteCount >= 3 &&
|
||||||
eb2->Bytes[0] == 1) {
|
eb2->Bytes[0] == 1) {
|
||||||
return (static_cast<uint32_t>(eb2->Bytes[2]) << 8) +
|
return ((uint32_t)(eb2->Bytes[2]) << 8) +
|
||||||
(static_cast<uint32_t>(eb2->Bytes[1]) << 0);
|
((uint32_t)(eb2->Bytes[1]) << 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -394,18 +436,19 @@ static uint32_t GetLoopCountGIF(const GifFileType* const gif) {
|
|||||||
|
|
||||||
// Get duration of 'n'th frame in milliseconds.
|
// Get duration of 'n'th frame in milliseconds.
|
||||||
static int GetFrameDurationGIF(GifFileType* gif, int n) {
|
static int GetFrameDurationGIF(GifFileType* gif, int n) {
|
||||||
GraphicsControlBlock gcb = GraphicsControlBlock();
|
GraphicsControlBlock gcb;
|
||||||
|
memset(&gcb, 0, sizeof(gcb));
|
||||||
DGifSavedExtensionToGCB(gif, n, &gcb);
|
DGifSavedExtensionToGCB(gif, n, &gcb);
|
||||||
return gcb.DelayTime * 10;
|
return gcb.DelayTime * 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if frame 'target' completely covers 'covered'.
|
// Returns true if frame 'target' completely covers 'covered'.
|
||||||
static bool CoversFrameGIF(const GifImageDesc& target,
|
static int CoversFrameGIF(const GifImageDesc* const target,
|
||||||
const GifImageDesc& covered) {
|
const GifImageDesc* const covered) {
|
||||||
return target.Left <= covered.Left &&
|
return target->Left <= covered->Left &&
|
||||||
covered.Left + covered.Width <= target.Left + target.Width &&
|
covered->Left + covered->Width <= target->Left + target->Width &&
|
||||||
target.Top <= covered.Top &&
|
target->Top <= covered->Top &&
|
||||||
covered.Top + covered.Height <= target.Top + target.Height;
|
covered->Top + covered->Height <= target->Top + target->Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void RemapPixelsGIF(const uint8_t* const src,
|
static void RemapPixelsGIF(const uint8_t* const src,
|
||||||
@ -425,65 +468,72 @@ static void RemapPixelsGIF(const uint8_t* const src,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ReadFrameGIF(const SavedImage* const gif_image,
|
static int ReadFrameGIF(const SavedImage* const gif_image,
|
||||||
const ColorMapObject* cmap, int transparent_color,
|
const ColorMapObject* cmap, int transparent_color,
|
||||||
int out_stride, uint8_t* const dst) {
|
int out_stride, uint8_t* const dst) {
|
||||||
const GifImageDesc& image_desc = gif_image->ImageDesc;
|
const GifImageDesc* image_desc = &gif_image->ImageDesc;
|
||||||
if (image_desc.ColorMap) {
|
const uint8_t* in;
|
||||||
cmap = image_desc.ColorMap;
|
uint8_t* out;
|
||||||
}
|
int j;
|
||||||
|
|
||||||
|
if (image_desc->ColorMap) cmap = image_desc->ColorMap;
|
||||||
|
|
||||||
if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
|
if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
|
||||||
fprintf(stderr, "Potentially corrupt color map.\n");
|
fprintf(stderr, "Potentially corrupt color map.\n");
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t* in = reinterpret_cast<uint8_t*>(gif_image->RasterBits);
|
in = (const uint8_t*)gif_image->RasterBits;
|
||||||
uint8_t* out =
|
out = dst + image_desc->Top * out_stride + image_desc->Left * kNumChannels;
|
||||||
dst + image_desc.Top * out_stride + image_desc.Left * kNumChannels;
|
|
||||||
|
|
||||||
for (int j = 0; j < image_desc.Height; ++j) {
|
for (j = 0; j < image_desc->Height; ++j) {
|
||||||
RemapPixelsGIF(in, cmap, transparent_color, image_desc.Width, out);
|
RemapPixelsGIF(in, cmap, transparent_color, image_desc->Width, out);
|
||||||
in += image_desc.Width;
|
in += image_desc->Width;
|
||||||
out += out_stride;
|
out += out_stride;
|
||||||
}
|
}
|
||||||
return true;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read animated GIF bitstream from 'filename' into 'AnimatedImage' struct.
|
// Read animated GIF bitstream from 'filename' into 'AnimatedImage' struct.
|
||||||
static bool ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
|
static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
|
||||||
bool dump_frames, const char dump_folder[]) {
|
int dump_frames, const char dump_folder[]) {
|
||||||
GifFileType* gif = DGifOpenFileName(filename, NULL);
|
uint32_t frame_count;
|
||||||
|
uint32_t canvas_width, canvas_height;
|
||||||
|
uint32_t i;
|
||||||
|
int gif_error;
|
||||||
|
GifFileType* gif;
|
||||||
|
|
||||||
|
gif = DGifOpenFileName(filename, NULL);
|
||||||
if (gif == NULL) {
|
if (gif == NULL) {
|
||||||
fprintf(stderr, "Could not read file: %s.\n", filename);
|
fprintf(stderr, "Could not read file: %s.\n", filename);
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int gif_error = DGifSlurp(gif);
|
gif_error = DGifSlurp(gif);
|
||||||
if (gif_error != GIF_OK) {
|
if (gif_error != GIF_OK) {
|
||||||
fprintf(stderr, "Could not parse image: %s.\n", filename);
|
fprintf(stderr, "Could not parse image: %s.\n", filename);
|
||||||
GIFDisplayError(gif, gif_error);
|
GIFDisplayError(gif, gif_error);
|
||||||
DGifCloseFile(gif, NULL);
|
DGifCloseFile(gif, NULL);
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animation properties.
|
// Animation properties.
|
||||||
image->canvas_width = static_cast<uint32_t>(gif->SWidth);
|
image->canvas_width = (uint32_t)gif->SWidth;
|
||||||
image->canvas_height = static_cast<uint32_t>(gif->SHeight);
|
image->canvas_height = (uint32_t)gif->SHeight;
|
||||||
if (image->canvas_width > MAX_CANVAS_SIZE ||
|
if (image->canvas_width > MAX_CANVAS_SIZE ||
|
||||||
image->canvas_height > MAX_CANVAS_SIZE) {
|
image->canvas_height > MAX_CANVAS_SIZE) {
|
||||||
fprintf(stderr, "Invalid canvas dimension: %d x %d\n",
|
fprintf(stderr, "Invalid canvas dimension: %d x %d\n",
|
||||||
image->canvas_width, image->canvas_height);
|
image->canvas_width, image->canvas_height);
|
||||||
DGifCloseFile(gif, NULL);
|
DGifCloseFile(gif, NULL);
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
image->loop_count = GetLoopCountGIF(gif);
|
image->loop_count = GetLoopCountGIF(gif);
|
||||||
image->bgcolor = GetBackgroundColorGIF(gif);
|
image->bgcolor = GetBackgroundColorGIF(gif);
|
||||||
|
|
||||||
const uint32_t frame_count = static_cast<uint32_t>(gif->ImageCount);
|
frame_count = (uint32_t)gif->ImageCount;
|
||||||
if (frame_count == 0) {
|
if (frame_count == 0) {
|
||||||
DGifCloseFile(gif, NULL);
|
DGifCloseFile(gif, NULL);
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image->canvas_width == 0 || image->canvas_height == 0) {
|
if (image->canvas_width == 0 || image->canvas_height == 0) {
|
||||||
@ -494,33 +544,38 @@ static bool ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
|
|||||||
if (image->canvas_width == 0 || image->canvas_height == 0) {
|
if (image->canvas_width == 0 || image->canvas_height == 0) {
|
||||||
fprintf(stderr, "Invalid canvas size in GIF.\n");
|
fprintf(stderr, "Invalid canvas size in GIF.\n");
|
||||||
DGifCloseFile(gif, NULL);
|
DGifCloseFile(gif, NULL);
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Allocate frames.
|
// Allocate frames.
|
||||||
AllocateFrames(image, frame_count);
|
AllocateFrames(image, frame_count);
|
||||||
|
|
||||||
const uint32_t canvas_width = image->canvas_width;
|
canvas_width = image->canvas_width;
|
||||||
const uint32_t canvas_height = image->canvas_height;
|
canvas_height = image->canvas_height;
|
||||||
|
|
||||||
// Decode and reconstruct frames.
|
// Decode and reconstruct frames.
|
||||||
for (uint32_t i = 0; i < frame_count; ++i) {
|
for (i = 0; i < frame_count; ++i) {
|
||||||
const int canvas_width_in_bytes = canvas_width * kNumChannels;
|
const int canvas_width_in_bytes = canvas_width * kNumChannels;
|
||||||
const SavedImage* const curr_gif_image = &gif->SavedImages[i];
|
const SavedImage* const curr_gif_image = &gif->SavedImages[i];
|
||||||
GraphicsControlBlock curr_gcb = GraphicsControlBlock();
|
GraphicsControlBlock curr_gcb;
|
||||||
|
DecodedFrame* curr_frame;
|
||||||
|
uint8_t* curr_rgba;
|
||||||
|
|
||||||
|
memset(&curr_gcb, 0, sizeof(curr_gcb));
|
||||||
DGifSavedExtensionToGCB(gif, i, &curr_gcb);
|
DGifSavedExtensionToGCB(gif, i, &curr_gcb);
|
||||||
|
|
||||||
DecodedFrame* const curr_frame = &image->frames[i];
|
curr_frame = &image->frames[i];
|
||||||
uint8_t* const curr_rgba = curr_frame->rgba.data();
|
curr_rgba = curr_frame->rgba;
|
||||||
curr_frame->duration = GetFrameDurationGIF(gif, i);
|
curr_frame->duration = GetFrameDurationGIF(gif, i);
|
||||||
|
|
||||||
if (i == 0) { // Initialize as transparent.
|
if (i == 0) { // Initialize as transparent.
|
||||||
curr_frame->is_key_frame = true;
|
curr_frame->is_key_frame = 1;
|
||||||
ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
|
ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
|
||||||
} else {
|
} else {
|
||||||
DecodedFrame* const prev_frame = &image->frames[i - 1];
|
DecodedFrame* const prev_frame = &image->frames[i - 1];
|
||||||
const GifImageDesc& prev_desc = gif->SavedImages[i - 1].ImageDesc;
|
const GifImageDesc* const prev_desc = &gif->SavedImages[i - 1].ImageDesc;
|
||||||
GraphicsControlBlock prev_gcb = GraphicsControlBlock();
|
GraphicsControlBlock prev_gcb;
|
||||||
|
memset(&prev_gcb, 0, sizeof(prev_gcb));
|
||||||
DGifSavedExtensionToGCB(gif, i - 1, &prev_gcb);
|
DGifSavedExtensionToGCB(gif, i - 1, &prev_gcb);
|
||||||
|
|
||||||
curr_frame->is_key_frame =
|
curr_frame->is_key_frame =
|
||||||
@ -530,32 +585,35 @@ static bool ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
|
|||||||
if (curr_frame->is_key_frame) { // Initialize as transparent.
|
if (curr_frame->is_key_frame) { // Initialize as transparent.
|
||||||
ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
|
ZeroFillCanvas(curr_rgba, canvas_width, canvas_height);
|
||||||
} else {
|
} else {
|
||||||
|
int prev_frame_disposed, curr_frame_opaque;
|
||||||
|
int prev_frame_completely_covered;
|
||||||
// Initialize with previous canvas.
|
// Initialize with previous canvas.
|
||||||
uint8_t* const prev_rgba = image->frames[i - 1].rgba.data();
|
uint8_t* const prev_rgba = image->frames[i - 1].rgba;
|
||||||
CopyCanvas(prev_rgba, curr_rgba, canvas_width, canvas_height);
|
CopyCanvas(prev_rgba, curr_rgba, canvas_width, canvas_height);
|
||||||
|
|
||||||
// Dispose previous frame rectangle.
|
// Dispose previous frame rectangle.
|
||||||
bool prev_frame_disposed =
|
prev_frame_disposed =
|
||||||
(prev_gcb.DisposalMode == DISPOSE_BACKGROUND ||
|
(prev_gcb.DisposalMode == DISPOSE_BACKGROUND ||
|
||||||
prev_gcb.DisposalMode == DISPOSE_PREVIOUS);
|
prev_gcb.DisposalMode == DISPOSE_PREVIOUS);
|
||||||
bool curr_frame_opaque =
|
curr_frame_opaque =
|
||||||
(curr_gcb.TransparentColor == NO_TRANSPARENT_COLOR);
|
(curr_gcb.TransparentColor == NO_TRANSPARENT_COLOR);
|
||||||
bool prev_frame_completely_covered =
|
prev_frame_completely_covered =
|
||||||
curr_frame_opaque &&
|
curr_frame_opaque &&
|
||||||
CoversFrameGIF(curr_gif_image->ImageDesc, prev_desc);
|
CoversFrameGIF(&curr_gif_image->ImageDesc, prev_desc);
|
||||||
|
|
||||||
if (prev_frame_disposed && !prev_frame_completely_covered) {
|
if (prev_frame_disposed && !prev_frame_completely_covered) {
|
||||||
switch (prev_gcb.DisposalMode) {
|
switch (prev_gcb.DisposalMode) {
|
||||||
case DISPOSE_BACKGROUND: {
|
case DISPOSE_BACKGROUND: {
|
||||||
ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
|
ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
|
||||||
prev_desc.Left, prev_desc.Top,
|
prev_desc->Left, prev_desc->Top,
|
||||||
prev_desc.Width, prev_desc.Height);
|
prev_desc->Width, prev_desc->Height);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DISPOSE_PREVIOUS: {
|
case DISPOSE_PREVIOUS: {
|
||||||
int src_frame_num = i - 2;
|
int src_frame_num = i - 2;
|
||||||
while (src_frame_num >= 0) {
|
while (src_frame_num >= 0) {
|
||||||
GraphicsControlBlock src_frame_gcb = GraphicsControlBlock();
|
GraphicsControlBlock src_frame_gcb;
|
||||||
|
memset(&src_frame_gcb, 0, sizeof(src_frame_gcb));
|
||||||
DGifSavedExtensionToGCB(gif, src_frame_num, &src_frame_gcb);
|
DGifSavedExtensionToGCB(gif, src_frame_num, &src_frame_gcb);
|
||||||
if (src_frame_gcb.DisposalMode != DISPOSE_PREVIOUS) break;
|
if (src_frame_gcb.DisposalMode != DISPOSE_PREVIOUS) break;
|
||||||
--src_frame_num;
|
--src_frame_num;
|
||||||
@ -564,17 +622,17 @@ static bool ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
|
|||||||
// Restore pixels inside previous frame rectangle to
|
// Restore pixels inside previous frame rectangle to
|
||||||
// corresponding pixels in source canvas.
|
// corresponding pixels in source canvas.
|
||||||
uint8_t* const src_frame_rgba =
|
uint8_t* const src_frame_rgba =
|
||||||
image->frames[src_frame_num].rgba.data();
|
image->frames[src_frame_num].rgba;
|
||||||
CopyFrameRectangle(src_frame_rgba, curr_rgba,
|
CopyFrameRectangle(src_frame_rgba, curr_rgba,
|
||||||
canvas_width_in_bytes,
|
canvas_width_in_bytes,
|
||||||
prev_desc.Left, prev_desc.Top,
|
prev_desc->Left, prev_desc->Top,
|
||||||
prev_desc.Width, prev_desc.Height);
|
prev_desc->Width, prev_desc->Height);
|
||||||
} else {
|
} else {
|
||||||
// Source canvas doesn't exist. So clear previous frame
|
// Source canvas doesn't exist. So clear previous frame
|
||||||
// rectangle to background.
|
// rectangle to background.
|
||||||
ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
|
ZeroFillFrameRect(curr_rgba, canvas_width_in_bytes,
|
||||||
prev_desc.Left, prev_desc.Top,
|
prev_desc->Left, prev_desc->Top,
|
||||||
prev_desc.Width, prev_desc.Height);
|
prev_desc->Width, prev_desc->Height);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -589,67 +647,65 @@ static bool ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
|
|||||||
if (!ReadFrameGIF(curr_gif_image, gif->SColorMap, curr_gcb.TransparentColor,
|
if (!ReadFrameGIF(curr_gif_image, gif->SColorMap, curr_gcb.TransparentColor,
|
||||||
canvas_width_in_bytes, curr_rgba)) {
|
canvas_width_in_bytes, curr_rgba)) {
|
||||||
DGifCloseFile(gif, NULL);
|
DGifCloseFile(gif, NULL);
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dump_frames) {
|
if (dump_frames) {
|
||||||
if (!DumpFrame(filename, dump_folder, i, curr_rgba,
|
if (!DumpFrame(filename, dump_folder, i, curr_rgba,
|
||||||
canvas_width, canvas_height)) {
|
canvas_width, canvas_height)) {
|
||||||
DGifCloseFile(gif, NULL);
|
DGifCloseFile(gif, NULL);
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DGifCloseFile(gif, NULL);
|
DGifCloseFile(gif, NULL);
|
||||||
return true;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static bool ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
|
static int ReadAnimatedGIF(const char filename[], AnimatedImage* const image,
|
||||||
bool dump_frames, const char dump_folder[]) {
|
int dump_frames, const char dump_folder[]) {
|
||||||
(void)filename;
|
(void)filename;
|
||||||
(void)image;
|
(void)image;
|
||||||
(void)dump_frames;
|
(void)dump_frames;
|
||||||
(void)dump_folder;
|
(void)dump_folder;
|
||||||
fprintf(stderr, "GIF support not compiled. Please install the libgif-dev "
|
fprintf(stderr, "GIF support not compiled. Please install the libgif-dev "
|
||||||
"package before building.\n");
|
"package before building.\n");
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // WEBP_HAVE_GIF
|
#endif // WEBP_HAVE_GIF
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
static bool ReadFile(const char filename[], std::string* filestr) {
|
int ReadAnimatedImage(const char filename[], AnimatedImage* const image,
|
||||||
ifstream fin(filename, ios::binary);
|
int dump_frames, const char dump_folder[]) {
|
||||||
if (!fin.good()) return false;
|
int ok = 0;
|
||||||
ostringstream strout;
|
WebPData webp_data;
|
||||||
strout << fin.rdbuf();
|
|
||||||
*filestr = strout.str();
|
|
||||||
fin.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReadAnimatedImage(const char filename[], AnimatedImage* const image,
|
WebPDataInit(&webp_data);
|
||||||
bool dump_frames, const char dump_folder[]) {
|
memset(image, 0, sizeof(*image));
|
||||||
std::string file_str;
|
|
||||||
if (!ReadFile(filename, &file_str)) {
|
if (!ExUtilReadFile(filename, &webp_data.bytes, &webp_data.size)) {
|
||||||
fprintf(stderr, "Error reading file: %s\n", filename);
|
fprintf(stderr, "Error reading file: %s\n", filename);
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsWebP(file_str)) {
|
if (IsWebP(&webp_data)) {
|
||||||
return ReadAnimatedWebP(filename, file_str, image, dump_frames,
|
ok = ReadAnimatedWebP(filename, &webp_data, image, dump_frames,
|
||||||
dump_folder);
|
dump_folder);
|
||||||
} else if (IsGIF(file_str)) {
|
} else if (IsGIF(&webp_data)) {
|
||||||
return ReadAnimatedGIF(filename, image, dump_frames, dump_folder);
|
ok = ReadAnimatedGIF(filename, image, dump_frames, dump_folder);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Unknown file type: %s. Supported file types are WebP and GIF\n",
|
"Unknown file type: %s. Supported file types are WebP and GIF\n",
|
||||||
filename);
|
filename);
|
||||||
return false;
|
ok = 0;
|
||||||
}
|
}
|
||||||
|
if (!ok) ClearAnimatedImage(image);
|
||||||
|
WebPDataClear(&webp_data);
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Accumulate(double v1, double v2, double* const max_diff,
|
static void Accumulate(double v1, double v2, double* const max_diff,
|
||||||
@ -660,25 +716,27 @@ static void Accumulate(double v1, double v2, double* const max_diff,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
|
void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
|
||||||
uint32_t width, uint32_t height, bool premultiply,
|
uint32_t width, uint32_t height, int premultiply,
|
||||||
int* const max_diff, double* const psnr) {
|
int* const max_diff, double* const psnr) {
|
||||||
const uint32_t stride = width * kNumChannels;
|
const uint32_t stride = width * kNumChannels;
|
||||||
const int kAlphaChannel = kNumChannels - 1;
|
const int kAlphaChannel = kNumChannels - 1;
|
||||||
double f_max_diff = 0.;
|
double f_max_diff = 0.;
|
||||||
double sse = 0.;
|
double sse = 0.;
|
||||||
for (uint32_t y = 0; y < height; ++y) {
|
uint32_t x, y;
|
||||||
for (uint32_t x = 0; x < stride; x += kNumChannels) {
|
for (y = 0; y < height; ++y) {
|
||||||
|
for (x = 0; x < stride; x += kNumChannels) {
|
||||||
|
int k;
|
||||||
const size_t offset = y * stride + x;
|
const size_t offset = y * stride + x;
|
||||||
const int alpha1 = rgba1[offset + kAlphaChannel];
|
const int alpha1 = rgba1[offset + kAlphaChannel];
|
||||||
const int alpha2 = rgba2[offset + kAlphaChannel];
|
const int alpha2 = rgba2[offset + kAlphaChannel];
|
||||||
Accumulate(alpha1, alpha2, &f_max_diff, &sse);
|
Accumulate(alpha1, alpha2, &f_max_diff, &sse);
|
||||||
if (!premultiply) {
|
if (!premultiply) {
|
||||||
for (int k = 0; k < kAlphaChannel; ++k) {
|
for (k = 0; k < kAlphaChannel; ++k) {
|
||||||
Accumulate(rgba1[offset + k], rgba2[offset + k], &f_max_diff, &sse);
|
Accumulate(rgba1[offset + k], rgba2[offset + k], &f_max_diff, &sse);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// premultiply R/G/B channels with alpha value
|
// premultiply R/G/B channels with alpha value
|
||||||
for (int k = 0; k < kAlphaChannel; ++k) {
|
for (k = 0; k < kAlphaChannel; ++k) {
|
||||||
Accumulate(rgba1[offset + k] * alpha1 / 255.,
|
Accumulate(rgba1[offset + k] * alpha1 / 255.,
|
||||||
rgba2[offset + k] * alpha2 / 255.,
|
rgba2[offset + k] * alpha2 / 255.,
|
||||||
&f_max_diff, &sse);
|
&f_max_diff, &sse);
|
||||||
@ -686,11 +744,11 @@ void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*max_diff = static_cast<int>(f_max_diff);
|
*max_diff = (int)f_max_diff;
|
||||||
if (*max_diff == 0) {
|
if (*max_diff == 0) {
|
||||||
*psnr = 99.; // PSNR when images are identical.
|
*psnr = 99.; // PSNR when images are identical.
|
||||||
} else {
|
} else {
|
||||||
sse /= stride * height;
|
sse /= stride * height;
|
||||||
*psnr = 10. * log10(255. * 255. / sse);
|
*psnr = 4.3429448 * log(255. * 255. / sse);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,34 +16,48 @@
|
|||||||
#include "webp/config.h"
|
#include "webp/config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "webp/types.h"
|
#include "webp/types.h"
|
||||||
|
|
||||||
struct DecodedFrame {
|
#ifdef __cplusplus
|
||||||
std::vector<uint8_t> rgba; // Decoded and reconstructed full frame.
|
extern "C" {
|
||||||
int duration; // Frame duration in milliseconds.
|
#endif
|
||||||
bool is_key_frame; // True if this frame is a key-frame.
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AnimatedImage {
|
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 {
|
||||||
uint32_t canvas_width;
|
uint32_t canvas_width;
|
||||||
uint32_t canvas_height;
|
uint32_t canvas_height;
|
||||||
uint32_t bgcolor;
|
uint32_t bgcolor;
|
||||||
uint32_t loop_count;
|
uint32_t loop_count;
|
||||||
std::vector<DecodedFrame> frames;
|
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.
|
// Read animated image file into 'AnimatedImage' struct.
|
||||||
// If 'dump_frames' is true, dump frames to 'dump_folder'.
|
// If 'dump_frames' is true, dump frames to 'dump_folder'.
|
||||||
bool ReadAnimatedImage(const char filename[], AnimatedImage* const image,
|
// Previous content of 'image' is obliterated.
|
||||||
bool dump_frames, const char dump_folder[]);
|
// 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.
|
// Given two RGBA buffers, calculate max pixel difference and PSNR.
|
||||||
// If 'premultiply' is true, R/G/B values will be pre-multiplied by the
|
// If 'premultiply' is true, R/G/B values will be pre-multiplied by the
|
||||||
// transparency before comparison.
|
// transparency before comparison.
|
||||||
void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
|
void GetDiffAndPSNR(const uint8_t rgba1[], const uint8_t rgba2[],
|
||||||
uint32_t width, uint32_t height, bool premultiply,
|
uint32_t width, uint32_t height, int premultiply,
|
||||||
int* const max_diff, double* const psnr);
|
int* const max_diff, double* const psnr);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // extern "C"
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // WEBP_EXAMPLES_ANIM_UTIL_H_
|
#endif // WEBP_EXAMPLES_ANIM_UTIL_H_
|
||||||
|
@ -62,19 +62,13 @@ EXTRA_FLAGS += -DWEBP_USE_THREAD
|
|||||||
EXTRA_LIBS += -lpthread
|
EXTRA_LIBS += -lpthread
|
||||||
|
|
||||||
# Extra flags to emulate C89 strictness with the full ANSI
|
# Extra flags to emulate C89 strictness with the full ANSI
|
||||||
EXTRA_C89_FLAGS = -Wextra -Wold-style-definition
|
EXTRA_FLAGS += -Wextra -Wold-style-definition
|
||||||
EXTRA_C89_FLAGS += -Wmissing-prototypes
|
EXTRA_FLAGS += -Wmissing-prototypes
|
||||||
EXTRA_C89_FLAGS += -Wmissing-declarations
|
EXTRA_FLAGS += -Wmissing-declarations
|
||||||
EXTRA_C89_FLAGS += -Wdeclaration-after-statement
|
EXTRA_FLAGS += -Wdeclaration-after-statement
|
||||||
EXTRA_C89_FLAGS += -Wshadow
|
EXTRA_FLAGS += -Wshadow
|
||||||
EXTRA_C89_FLAGS += -Wformat-security -Wformat-nonliteral
|
EXTRA_FLAGS += -Wformat-security -Wformat-nonliteral
|
||||||
# EXTRA_C89_FLAGS += -Wvla
|
# EXTRA_FLAGS += -Wvla
|
||||||
|
|
||||||
EXTRA_CXX_FLAGS = -Wextra
|
|
||||||
EXTRA_CXX_FLAGS += -Wmissing-declarations
|
|
||||||
EXTRA_CXX_FLAGS += -Wshadow
|
|
||||||
EXTRA_CXX_FLAGS += -Wformat-security -Wformat-nonliteral
|
|
||||||
# EXTRA_CXX_FLAGS += -Wvla
|
|
||||||
|
|
||||||
# SSE4.1-specific flags:
|
# SSE4.1-specific flags:
|
||||||
ifeq ($(HAVE_SSE41), 1)
|
ifeq ($(HAVE_SSE41), 1)
|
||||||
@ -97,11 +91,8 @@ endif
|
|||||||
AR = ar
|
AR = ar
|
||||||
ARFLAGS = r
|
ARFLAGS = r
|
||||||
CPPFLAGS = -Isrc/ -Wall
|
CPPFLAGS = -Isrc/ -Wall
|
||||||
COMMONFLAGS = -O3 -DNDEBUG $(EXTRA_FLAGS)
|
CFLAGS = -O3 -DNDEBUG $(EXTRA_FLAGS)
|
||||||
CC = gcc
|
CC = gcc
|
||||||
CFLAGS = $(COMMONFLAGS) $(EXTRA_C89_FLAGS)
|
|
||||||
CXX = g++
|
|
||||||
CXXFLAGS = $(COMMONFLAGS) $(EXTRA_CXX_FLAGS)
|
|
||||||
INSTALL = install
|
INSTALL = install
|
||||||
GROFF = /usr/bin/groff
|
GROFF = /usr/bin/groff
|
||||||
COL = /usr/bin/col
|
COL = /usr/bin/col
|
||||||
@ -299,18 +290,18 @@ HDRS = \
|
|||||||
OUT_LIBS = examples/libexample_util.a src/libwebpdecoder.a src/libwebp.a
|
OUT_LIBS = examples/libexample_util.a src/libwebpdecoder.a src/libwebp.a
|
||||||
EXTRA_LIB = src/libwebpextras.a
|
EXTRA_LIB = src/libwebpextras.a
|
||||||
OUT_EXAMPLES = examples/cwebp examples/dwebp
|
OUT_EXAMPLES = examples/cwebp examples/dwebp
|
||||||
EXTRA_EXAMPLES = examples/gif2webp examples/vwebp examples/webpmux
|
EXTRA_EXAMPLES = examples/gif2webp examples/vwebp examples/webpmux \
|
||||||
EXTRA_CXX_EXAMPLES = examples/anim_diff
|
examples/anim_diff
|
||||||
|
|
||||||
OUTPUT = $(OUT_LIBS) $(OUT_EXAMPLES)
|
OUTPUT = $(OUT_LIBS) $(OUT_EXAMPLES)
|
||||||
ifeq ($(MAKECMDGOALS),clean)
|
ifeq ($(MAKECMDGOALS),clean)
|
||||||
OUTPUT += $(EXTRA_EXAMPLES) $(EXTRA_CXX_EXAMPLES)
|
OUTPUT += $(EXTRA_EXAMPLES)
|
||||||
OUTPUT += src/demux/libwebpdemux.a src/mux/libwebpmux.a $(EXTRA_LIB)
|
OUTPUT += src/demux/libwebpdemux.a src/mux/libwebpmux.a $(EXTRA_LIB)
|
||||||
OUTPUT += examples/libgifdec.a examples/libanim_util.a
|
OUTPUT += examples/libgifdec.a examples/libanim_util.a
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ex: $(OUT_EXAMPLES)
|
ex: $(OUT_EXAMPLES)
|
||||||
all: ex $(EXTRA_EXAMPLES) $(EXTRA_CXX_EXAMPLES)
|
all: ex $(EXTRA_EXAMPLES)
|
||||||
extras: $(EXTRA_LIB)
|
extras: $(EXTRA_LIB)
|
||||||
|
|
||||||
$(EX_FORMAT_DEC_OBJS): %.o: %.h
|
$(EX_FORMAT_DEC_OBJS): %.o: %.h
|
||||||
@ -326,9 +317,6 @@ src/utils/bit_writer.o: src/utils/endian_inl.h
|
|||||||
%.o: %.c $(HDRS)
|
%.o: %.c $(HDRS)
|
||||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||||
|
|
||||||
%.o: %.cc $(HDRS)
|
|
||||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@
|
|
||||||
|
|
||||||
examples/libanim_util.a: $(ANIM_UTIL_OBJS)
|
examples/libanim_util.a: $(ANIM_UTIL_OBJS)
|
||||||
examples/libexample_util.a: $(EX_UTIL_OBJS)
|
examples/libexample_util.a: $(EX_UTIL_OBJS)
|
||||||
examples/libgifdec.a: $(GIFDEC_OBJS)
|
examples/libgifdec.a: $(GIFDEC_OBJS)
|
||||||
@ -349,7 +337,8 @@ examples/vwebp: examples/vwebp.o
|
|||||||
examples/webpmux: examples/webpmux.o
|
examples/webpmux: examples/webpmux.o
|
||||||
|
|
||||||
examples/anim_diff: examples/libanim_util.a examples/libgifdec.a
|
examples/anim_diff: examples/libanim_util.a examples/libgifdec.a
|
||||||
examples/anim_diff: src/demux/libwebpdemux.a src/libwebp.a
|
examples/anim_diff: src/demux/libwebpdemux.a examples/libexample_util.a
|
||||||
|
examples/anim_diff: src/libwebp.a
|
||||||
examples/anim_diff: EXTRA_LIBS += $(GIF_LIBS)
|
examples/anim_diff: EXTRA_LIBS += $(GIF_LIBS)
|
||||||
examples/anim_diff: EXTRA_FLAGS += -DWEBP_HAVE_GIF
|
examples/anim_diff: EXTRA_FLAGS += -DWEBP_HAVE_GIF
|
||||||
examples/cwebp: examples/libexample_util.a src/libwebp.a
|
examples/cwebp: examples/libexample_util.a src/libwebp.a
|
||||||
@ -370,9 +359,6 @@ examples/webpmux: src/libwebpdecoder.a
|
|||||||
$(OUT_EXAMPLES) $(EXTRA_EXAMPLES):
|
$(OUT_EXAMPLES) $(EXTRA_EXAMPLES):
|
||||||
$(CC) -o $@ $^ $(LDFLAGS)
|
$(CC) -o $@ $^ $(LDFLAGS)
|
||||||
|
|
||||||
$(EXTRA_CXX_EXAMPLES):
|
|
||||||
$(CXX) -o $@ $^ $(LDFLAGS)
|
|
||||||
|
|
||||||
dist: DESTDIR := dist
|
dist: DESTDIR := dist
|
||||||
dist: OUT_EXAMPLES += $(EXTRA_EXAMPLES)
|
dist: OUT_EXAMPLES += $(EXTRA_EXAMPLES)
|
||||||
dist: all
|
dist: all
|
||||||
|
Loading…
Reference in New Issue
Block a user