diff options
author | Eino-Ville Talvala <etalvala@google.com> | 2018-11-15 15:49:02 -0800 |
---|---|---|
committer | Eino-Ville Talvala <etalvala@google.com> | 2018-11-15 16:07:24 -0800 |
commit | 09f199a694ef5b956cabc368e40ab5ca11c64044 (patch) | |
tree | 456d184a3817c8b6524a90fc2da60893a2fc1895 /internal | |
parent | 2d25fc4f6a7e7453f877958a2f3f59b6cc588ca4 (diff) | |
download | dynamic_depth-09f199a694ef5b956cabc368e40ab5ca11c64044.tar.gz |
Initial commit of libdynamic_depth
Dynamic depth is a standard for embedding depth maps and
other similar extensions into standard image files like JPEG.
Test: m libdynamic_depth
Bug: 109735087
Bug: 119211681
Change-Id: I0103b7d47e60dc8e3a3b277456903d76f727926f
Diffstat (limited to 'internal')
62 files changed, 9782 insertions, 0 deletions
diff --git a/internal/base/disable_warnings.h b/internal/base/disable_warnings.h new file mode 100644 index 0000000..bf5a9d6 --- /dev/null +++ b/internal/base/disable_warnings.h @@ -0,0 +1,15 @@ +// This is not your usual header guard. The macro +// PHOTOS_EDITING_FORMATS_DYNAMIC_DEPTH_INTERNAL_BASE_WARNINGS_DISABLED shows up +// again in reenable_warnings.h. +#ifndef DYNAMIC_DEPTH_WARNINGS_DISABLED // NOLINT +#define DYNAMIC_DEPTH_WARNINGS_DISABLED + +#ifdef _MSC_VER +#pragma warning(push) +// Disable the warning C4251 which is triggered by stl classes in +// xmpmeta's public interface. To quote MSDN: "C4251 can be ignored " +// "if you are deriving from a type in the Standard C++ Library" +#pragma warning(disable : 4251) +#endif + +#endif // DYNAMIC_DEPTH_WARNINGS_DISABLED diff --git a/internal/base/integral_types.h b/internal/base/integral_types.h new file mode 100644 index 0000000..dd8e8e1 --- /dev/null +++ b/internal/base/integral_types.h @@ -0,0 +1,113 @@ +// This code is compiled directly on many platforms, including client +// platforms like Windows, Mac, and embedded systems. Before making +// any changes here, make sure that you're not breaking any platforms. +// +// MOE:begin_strip +// This file is open source. You may export it with your open source projects +// as long as you use MOE to strip proprietary comments. +// MOE:end_strip + +#ifndef DYNAMIC_DEPTH_INTERNAL_BASE_INTEGRAL_TYPES_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_BASE_INTEGRAL_TYPES_H_ // NOLINT + +// These typedefs are also defined in base/swig/google.swig. In the +// SWIG environment, we use those definitions and avoid duplicate +// definitions here with an ifdef. The definitions should be the +// same in both files, and ideally be only defined in this file. +#ifndef SWIG // NOLINT +// Standard typedefs +// MOE:begin_strip +// All Google2 code is compiled with -funsigned-char to make "char" +// unsigned. Google2 code therefore doesn't need a "uchar" type. +// MOE:end_strip +// Signed integer types with width of exactly 8, 16, 32, or 64 bits +// respectively, for use when exact sizes are required. +typedef signed char schar; +typedef signed char int8; +typedef short int16; // NOLINT +typedef int int32; +#ifdef COMPILER_MSVC +typedef __int64 int64; +#else +typedef long long int64; // NOLINT +#endif /* COMPILER_MSVC */ + +// NOTE: unsigned types are DANGEROUS in loops and other arithmetical +// places. Use the signed types unless your variable represents a bit +// pattern (eg a hash value) or you really need the extra bit. Do NOT +// use 'unsigned' to express "this value should always be positive"; +// use assertions for this. + +// Unsigned integer types with width of exactly 8, 16, 32, or 64 bits +// respectively, for use when exact sizes are required. +typedef unsigned char uint8; +typedef unsigned short uint16; // NOLINT +typedef unsigned int uint32; +#ifdef COMPILER_MSVC +typedef unsigned __int64 uint64; +#else +typedef unsigned long long uint64; +#endif /* COMPILER_MSVC */ + +// A type to represent a Unicode code-point value. As of Unicode 4.0, +// such values require up to 21 bits. +// (For type-checking on pointers, make this explicitly signed, +// and it should always be the signed version of whatever int32 is.) +typedef signed int char32; + +// A type to represent a natural machine word (for e.g. efficiently +// scanning through memory for checksums or index searching). Don't use +// this for storing normal integers. Ideally this would be just +// unsigned int, but our 64-bit architectures use the LP64 model +// (http://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models), hence +// their ints are only 32 bits. We want to use the same fundamental +// type on all archs if possible to preserve *printf() compatibility. +typedef unsigned long uword_t; // NOLINT + +#endif /* SWIG */ + +// long long macros to be used because gcc and vc++ use different suffixes, +// and different size specifiers in format strings +#undef GG_LONGLONG +#undef GG_ULONGLONG +#undef GG_LL_FORMAT + +#ifdef COMPILER_MSVC /* if Visual C++ */ + +// VC++ long long suffixes +#define GG_LONGLONG(x) x##I64 +#define GG_ULONGLONG(x) x##UI64 + +// Length modifier in printf format string for int64's (e.g. within %d) +#define GG_LL_FORMAT "I64" // As in printf("%I64d", ...) +#define GG_LL_FORMAT_W L"I64" + +#else /* not Visual C++ */ + +#define GG_LONGLONG(x) x##LL +#define GG_ULONGLONG(x) x##ULL +#define GG_LL_FORMAT "ll" // As in "%lld". Note that "q" is poor form also. +#define GG_LL_FORMAT_W L"ll" + +#endif // COMPILER_MSVC + +static const uint8 kuint8max = ((uint8)0xFF); +static const uint16 kuint16max = ((uint16)0xFFFF); +static const uint32 kuint32max = ((uint32)0xFFFFFFFF); +static const uint64 kuint64max = ((uint64)GG_LONGLONG(0xFFFFFFFFFFFFFFFF)); +static const int8 kint8min = ((int8)~0x7F); +static const int8 kint8max = ((int8)0x7F); +static const int16 kint16min = ((int16)~0x7FFF); +static const int16 kint16max = ((int16)0x7FFF); +static const int32 kint32min = ((int32)~0x7FFFFFFF); +static const int32 kint32max = ((int32)0x7FFFFFFF); +static const int64 kint64min = ((int64)GG_LONGLONG(~0x7FFFFFFFFFFFFFFF)); +static const int64 kint64max = ((int64)GG_LONGLONG(0x7FFFFFFFFFFFFFFF)); + +// TODO(jyrki): remove this eventually. +// No object has kIllegalFprint as its Fingerprint. +typedef uint64 Fprint; +static const Fprint kIllegalFprint = 0; +static const Fprint kMaxFprint = GG_ULONGLONG(0xFFFFFFFFFFFFFFFF); + +#endif // DYNAMIC_DEPTH_INTERNAL_BASE_INTEGRAL_TYPES_H_ // NOLINT diff --git a/internal/base/macros.h b/internal/base/macros.h new file mode 100644 index 0000000..925322f --- /dev/null +++ b/internal/base/macros.h @@ -0,0 +1,56 @@ +// This code is compiled directly on many platforms, including client +// platforms like Windows, Mac, and embedded systems. Before making +// any changes here, make sure that you're not breaking any platforms. +// + +#ifndef DYNAMIC_DEPTH_INTERNAL_BASE_MACROS_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_BASE_MACROS_H_ // NOLINT + +#include <stddef.h> // For size_t +#include "base/port.h" + +// The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through +// between switch labels: +// switch (x) { +// case 40: +// case 41: +// if (truth_is_out_there) { +// ++x; +// FALLTHROUGH_INTENDED; // Use instead of/along with annotations in +// // comments. +// } else { +// return x; +// } +// case 42: +// ... +// +// As shown in the example above, the FALLTHROUGH_INTENDED macro should be +// followed by a semicolon. It is designed to mimic control-flow statements +// like 'break;', so it can be placed in most places where 'break;' can, but +// only if there are no statements on the execution path between it and the +// next switch label. +// +// When compiled with clang in C++11 mode, the FALLTHROUGH_INTENDED macro is +// expanded to [[clang::fallthrough]] attribute, which is analysed when +// performing switch labels fall-through diagnostic ('-Wimplicit-fallthrough'). +// See clang documentation on language extensions for details: +// http://clang.llvm.org/docs/AttributeReference.html#fallthrough-clang-fallthrough +// +// When used with unsupported compilers, the FALLTHROUGH_INTENDED macro has no +// effect on diagnostics. +// +// In either case this macro has no effect on runtime behavior and performance +// of code. +#if defined(__clang__) && defined(LANG_CXX11) && defined(__has_warning) +#if __has_feature(cxx_attributes) && __has_warning("-Wimplicit-fallthrough") +#define FALLTHROUGH_INTENDED [[clang::fallthrough]] // NOLINT +#endif +#endif + +#ifndef FALLTHROUGH_INTENDED // NOLINT +#define FALLTHROUGH_INTENDED \ + do { \ + } while (0) +#endif + +#endif // DYNAMIC_DEPTH_INTERNAL_BASE_MACROS_H_ // NOLINT diff --git a/internal/base/port.h b/internal/base/port.h new file mode 100644 index 0000000..daf0edd --- /dev/null +++ b/internal/base/port.h @@ -0,0 +1,1511 @@ +// These are weird things we need to do to get this compiling on +// random systems (and on SWIG). +// + +#ifndef DYNAMIC_DEPTH_INTERNAL_BASE_PORT_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_BASE_PORT_H_ // NOLINT + +#include <limits.h> // So we can set the bounds of our types +#include <stdlib.h> // for free() +#include <string.h> // for memcpy() + +#if defined(__APPLE__) +// OSX has type names *_t, so we define these aliases. +#include <inttypes.h> +#include <stdint.h> + +typedef uint64_t uint64; +typedef uint32_t uint32; +typedef uint16_t uint16; +typedef uint8_t uint8; + +typedef int64_t int64; +typedef int32_t int32; +typedef int16_t int16; +typedef int8_t int8; +#endif + +#define DYNAMIC_DEPTH_INTERNAL_EXPORT // NOLINT + +#if defined(OS_CYGWIN) +#error "Cygwin is not supported." +#endif + +#if defined(__CYGWIN__) +#error "Cygwin is not supported." +#endif + +#if defined(__APPLE__) +// Currently, blaze supports iOS yet doesn't define a flag. Mac users have +// traditionally defined OS_MACOSX themselves via other build systems, since mac +// hasn't been supported by blaze. +#include <TargetConditionals.h> +#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#ifndef OS_IOS // NOLINT +#define OS_IOS 1 +#endif +#define SUPPRESS_MOBILE_IOS_BASE_PORT_H +#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#endif // defined(__APPLE__) + +#if defined(OS_MACOSX) || defined(OS_IOS) +// This was added for getpagesize(), which is no longer used here. +// Clients incorrectly depend on this include. +#include <unistd.h> +#elif defined(OS_CYGWIN) || defined(__ANDROID__) +#include <malloc.h> // for memalign() +#elif defined(COMPILER_MSVC) +#include <stdio.h> // declare snprintf/vsnprintf before overriding +#endif + +#include "base/integral_types.h" + +// We support gcc 4.7 and later. +#if defined(__GNUC__) && !defined(__clang__) +#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7) +#error "This package requires gcc 4.7 or higher" +#endif +#endif + +// We support MSVC++ 12.0 and later. +#if defined(_MSC_VER) && _MSC_VER < 1800 +#error "This package requires _MSC_VER of 1800 or higher" +#endif + +// We support Apple Xcode clang 4.2.1 (version 421.11.65) and later. +// This corresponds to Apple Xcode version 4.5. +#if defined(__apple_build_version__) && __apple_build_version__ < 4211165 +#error "This package requires __apple_build_version__ of 4211165 or higher" +#endif + +// Must happens before inttypes.h inclusion */ +#if defined(OS_MACOSX) +/* From MacOSX's inttypes.h: + * "C++ implementations should define these macros only when + * __STDC_FORMAT_MACROS is defined before <inttypes.h> is included." */ +#ifndef __STDC_FORMAT_MACROS // NOLINT +#define __STDC_FORMAT_MACROS +#endif /* __STDC_FORMAT_MACROS */ +#endif /* OS_MACOSX */ + +/* Default for most OSes */ +/* We use SIGPWR since that seems unlikely to be used for other reasons. */ +#define GOOGLE_OBSCURE_SIGNAL SIGPWR + +#if defined OS_LINUX || defined OS_CYGWIN || defined OS_ANDROID || \ + defined(__ANDROID__) +// _BIG_ENDIAN +#include <endian.h> +#endif + +#if defined OS_LINUX || defined OS_CYGWIN + +// GLIBC-related macros. +#include <features.h> + +#ifndef __GLIBC_PREREQ // NOLINT +#define __GLIBC_PREREQ(a, b) 0 // not a GLIBC system +#endif + +// The uint mess: +// mysql.h sets _GNU_SOURCE which sets __USE_MISC in <features.h> +// sys/types.h typedefs uint if __USE_MISC +// mysql typedefs uint if HAVE_UINT not set +// The following typedef is carefully considered, and should not cause +// any clashes +#if !defined(__USE_MISC) +#if !defined(HAVE_UINT) +#define HAVE_UINT 1 +typedef unsigned int uint; +#endif +#if !defined(HAVE_USHORT) +#define HAVE_USHORT 1 +typedef unsigned short ushort; +#endif +#if !defined(HAVE_ULONG) +#define HAVE_ULONG 1 +typedef unsigned long ulong; +#endif +#endif + +#if defined(__cplusplus) +#include <cstddef> // For _GLIBCXX macros +#endif + +#if !defined(HAVE_TLS) && \ + (defined(_LIBCPP_VERSION) || defined(_GLIBCXX_HAVE_TLS)) && \ + (defined(ARCH_K8) || defined(ARCH_PPC) || defined(ARCH_ARM)) +#define HAVE_TLS 1 +#endif + +#elif defined OS_FREEBSD + +// _BIG_ENDIAN +#include <machine/endian.h> + +#elif defined(OS_MACOSX) || defined(OS_IOS) + +// BIG_ENDIAN +#include <machine/endian.h> // NOLINT(build/include) +/* Let's try and follow the Linux convention */ +#define __BYTE_ORDER BYTE_ORDER +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#define __BIG_ENDIAN BIG_ENDIAN + +#endif + +// The following guarantees declaration of the byte swap functions, and +// defines __BYTE_ORDER for MSVC +#ifdef COMPILER_MSVC +#include <stdlib.h> // NOLINT(build/include) +#define __BYTE_ORDER __LITTLE_ENDIAN +#define bswap_16(x) _byteswap_ushort(x) +#define bswap_32(x) _byteswap_ulong(x) +#define bswap_64(x) _byteswap_uint64(x) + +#elif defined(OS_MACOSX) || defined(OS_IOS) +// Mac OS X / Darwin features +#include <libkern/OSByteOrder.h> +#define bswap_16(x) OSSwapInt16(x) +#define bswap_32(x) OSSwapInt32(x) +#define bswap_64(x) OSSwapInt64(x) + +#elif defined(__GLIBC__) || defined(__CYGWIN__) +#include <byteswap.h> // IWYU pragma: export + +#else + +static inline uint16 bswap_16(uint16 x) { + return static_cast<uint16>(((x & 0xFF) << 8) | ((x & 0xFF00) >> 8)); +} +#define bswap_16(x) bswap_16(x) +static inline uint32 bswap_32(uint32 x) { + return (((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) | + ((x & 0xFF000000) >> 24)); +} +#define bswap_32(x) bswap_32(x) +static inline uint64 bswap_64(uint64 x) { + return (((x & GG_ULONGLONG(0xFF)) << 56) | + ((x & GG_ULONGLONG(0xFF00)) << 40) | + ((x & GG_ULONGLONG(0xFF0000)) << 24) | + ((x & GG_ULONGLONG(0xFF000000)) << 8) | + ((x & GG_ULONGLONG(0xFF00000000)) >> 8) | + ((x & GG_ULONGLONG(0xFF0000000000)) >> 24) | + ((x & GG_ULONGLONG(0xFF000000000000)) >> 40) | + ((x & GG_ULONGLONG(0xFF00000000000000)) >> 56)); +} +#define bswap_64(x) bswap_64(x) + +#endif + +// define the macros IS_LITTLE_ENDIAN or IS_BIG_ENDIAN +// using the above endian definitions from endian.h if +// endian.h was included +#ifdef __BYTE_ORDER +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define IS_LITTLE_ENDIAN +#endif + +#if __BYTE_ORDER == __BIG_ENDIAN +#define IS_BIG_ENDIAN +#endif + +#else + +#if defined(__LITTLE_ENDIAN__) +#define IS_LITTLE_ENDIAN +#elif defined(__BIG_ENDIAN__) +#define IS_BIG_ENDIAN +#endif + +// there is also PDP endian ... + +#endif // __BYTE_ORDER + +// Define the OS's path separator +#ifdef __cplusplus // C won't merge duplicate const variables at link time +// Some headers provide a macro for this (GCC's system.h), remove it so that we +// can use our own. +#undef PATH_SEPARATOR +#if defined(OS_WINDOWS) +const char PATH_SEPARATOR = '\\'; +#else +const char PATH_SEPARATOR = '/'; +#endif +#endif + +// Windows has O_BINARY as a flag to open() (like "b" for fopen). +// Linux doesn't need make this distinction. +#if defined OS_LINUX && !defined O_BINARY +#define O_BINARY 0 +#endif + +#ifdef COMPILER_MSVC +// doesn't have uid_t +typedef int uid_t; +#endif + +// Mac OS X / Darwin and iOS features + +#if defined(OS_MACOSX) || defined(OS_IOS) + +// For mmap, Linux defines both MAP_ANONYMOUS and MAP_ANON and says MAP_ANON is +// deprecated. In Darwin, MAP_ANON is all there is. +#if !defined MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif + +// Linux has this in <sys/cdefs.h> +#define __ptr_t void * + +// Linux has this in <linux/errno.h> +#define EXFULL ENOMEM // not really that great a translation... + +// Mach-O supports sections (albeit with small names), but doesn't have +// vars at the beginning and end. Instead you should call the function +// getsectdata("__DATA", name, &size). +#define HAVE_ATTRIBUTE_SECTION 1 + +// Any function with ATTRIBUTE_SECTION must not be inlined, or it will +// be placed into whatever section its caller is placed into. +#define ATTRIBUTE_SECTION(name) \ + __attribute__((section("__DATA, " #name))) __attribute__((noinline)) + +#define ENUM_DYLD_BOOL // so that we don't pollute the global namespace +extern "C" { +#include <mach-o/dyld.h> +#include <mach-o/getsect.h> +} +class AssignAttributeStartEnd { + public: + AssignAttributeStartEnd(const char *name, char **pstart, char **pend) { + // Find out what dynamic library name is defined in + for (int i = _dyld_image_count() - 1; i >= 0; --i) { + const mach_header *hdr = _dyld_get_image_header(i); + uint32_t len; + *pstart = getsectdatafromheader(hdr, "__DATA", name, &len); + if (*pstart) { // NULL if not defined in this dynamic library + *pstart += _dyld_get_image_vmaddr_slide(i); // correct for reloc + *pend = *pstart + len; + return; + } + } + // If we get here, not defined in a dll at all. See if defined statically. + // don't ask me why this type isn't uint32_t too... + unsigned long len; // NOLINT + *pstart = getsectdata("__DATA", name, &len); + *pend = *pstart + len; + } +}; + +// 1) DEFINE_ATTRIBUTE_SECTION_VARS: must be called once per unique +// name. You want to make sure this is executed before any +// DECLARE_ATTRIBUTE_SECTION_VARS; the easiest way is to put them +// in the same .cc file. Put this call at the global level. +// 2) INIT_ATTRIBUTE_SECTION_VARS: you can scatter calls to this in +// multiple places to help ensure execution before any +// DECLARE_ATTRIBUTE_SECTION_VARS. You must have at least one +// DEFINE, but you can have many INITs. Put each in its own scope. +// 3) DECLARE_ATTRIBUTE_SECTION_VARS: must be called before using +// ATTRIBUTE_SECTION_START or ATTRIBUTE_SECTION_STOP on a name. +// Put this call at the global level. +#define DECLARE_ATTRIBUTE_SECTION_VARS(name) \ + extern char *__start_##name; \ + extern char *__stop_##name; + +#define INIT_ATTRIBUTE_SECTION_VARS(name) \ + DECLARE_ATTRIBUTE_SECTION_VARS(name); \ + static const AssignAttributeStartEnd __assign_##name(#name, &__start_##name, \ + &__stop_##name) + +#define DEFINE_ATTRIBUTE_SECTION_VARS(name) \ + char *__start_##name, *__stop_##name; \ + INIT_ATTRIBUTE_SECTION_VARS(name) + +// Darwin doesn't have strnlen. No comment. +inline size_t strnlen(const char *s, size_t maxlen) { + const char *end = (const char *)memchr(s, '\0', maxlen); + if (end) return end - s; + return maxlen; +} + +// Doesn't exist on OSX. +#define MSG_NOSIGNAL 0 + +// No SIGPWR on MacOSX. SIGINFO seems suitably obscure. +#undef GOOGLE_OBSCURE_SIGNAL +#define GOOGLE_OBSCURE_SIGNAL SIGINFO + +#elif defined(OS_CYGWIN) // Cygwin-specific behavior. + +#if defined(__CYGWIN32__) +#define __WORDSIZE 32 +#else +// It's probably possible to support 64-bit, but the #defines will need checked. +#error "Cygwin is currently only 32-bit." +#endif + +// No signalling on Windows. +#undef GOOGLE_OBSCURE_SIGNAL +#define GOOGLE_OBSCURE_SIGNAL 0 + +struct stack_t { + void *ss_sp; + int ss_flags; + size_t ss_size; +}; +inline int sigaltstack(stack_t *ss, stack_t *oss) { return 0; } + +#define PTHREAD_STACK_MIN 0 // Not provided by cygwin + +// Scans memory for a character. +// memrchr is used in a few places, but it's linux-specific. +inline void *memrchr(const void *bytes, int find_char, size_t len) { + const unsigned char *cursor = + reinterpret_cast<const unsigned char *>(bytes) + len - 1; + unsigned char actual_char = find_char; + for (; cursor >= bytes; --cursor) { + if (*cursor == actual_char) { + return const_cast<void *>(reinterpret_cast<const void *>(cursor)); + } + } + return NULL; +} + +#endif + +// Klocwork static analysis tool's C/C++ compiler kwcc +#if defined(__KLOCWORK__) +#define STATIC_ANALYSIS +#endif // __KLOCWORK__ + +// GCC-specific features + +#if (defined(COMPILER_GCC3) || defined(OS_MACOSX) || defined(OS_IOS)) && \ + !defined(SWIG) + +// +// Tell the compiler to do printf format string checking if the +// compiler supports it; see the 'format' attribute in +// <http://gcc.gnu.org/onlinedocs/gcc-4.3.0/gcc/Function-Attributes.html>. +// +// N.B.: As the GCC manual states, "[s]ince non-static C++ methods +// have an implicit 'this' argument, the arguments of such methods +// should be counted from two, not one." +// +#define PRINTF_ATTRIBUTE(string_index, first_to_check) \ + __attribute__((__format__(__printf__, string_index, first_to_check))) +#define SCANF_ATTRIBUTE(string_index, first_to_check) \ + __attribute__((__format__(__scanf__, string_index, first_to_check))) + +// Cache line alignment +#if defined(__i386__) || defined(__x86_64__) +#define CACHELINE_SIZE 64 +#elif defined(__powerpc64__) +#define CACHELINE_SIZE 128 +#elif defined(__aarch64__) +// We would need to read special regiter ctr_el0 to find out L1 dcache size. +// This value is a good estimate based on a real aarch64 machine. +#define CACHELINE_SIZE 64 +#elif defined(__arm__) +// Cache line sizes for ARM: These values are not strictly correct since +// cache line sizes depend on implementations, not architectures. There +// are even implementations with cache line sizes configurable at boot +// time. +#if defined(__ARM_ARCH_5T__) +#define CACHELINE_SIZE 32 +#elif defined(__ARM_ARCH_7A__) +#define CACHELINE_SIZE 64 +#endif +#endif + +#ifndef CACHELINE_SIZE // NOLINT +// A reasonable default guess. Note that overestimates tend to waste more +// space, while underestimates tend to waste more time. +#define CACHELINE_SIZE 64 +#endif + +#define CACHELINE_ALIGNED __attribute__((aligned(CACHELINE_SIZE))) + +// +// Prevent the compiler from complaining about or optimizing away variables +// that appear unused +#ifndef DDEPTH_ATTRIBUTE_UNUSED // NOLINT +#undef DDEPTH_ATTRIBUTE_UNUSED +#define DDEPTH_ATTRIBUTE_UNUSED __attribute__((__unused__)) +#endif // DDEPTH_ATTRIBUTE_UNUSED + +// +// For functions we want to force inline or not inline. +// Introduced in gcc 3.1. +#define ATTRIBUTE_ALWAYS_INLINE __attribute__((always_inline)) +#define HAVE_ATTRIBUTE_ALWAYS_INLINE 1 +#define ATTRIBUTE_NOINLINE __attribute__((noinline)) +#define HAVE_ATTRIBUTE_NOINLINE 1 + +// For weak functions +#undef ATTRIBUTE_WEAK +#define ATTRIBUTE_WEAK __attribute__((weak)) +#define HAVE_ATTRIBUTE_WEAK 1 + +// Tell the compiler to use "initial-exec" mode for a thread-local variable. +// See http://people.redhat.com/drepper/tls.pdf for the gory details. +#define ATTRIBUTE_INITIAL_EXEC __attribute__((tls_model("initial-exec"))) + +// Tell the compiler either that a particular function parameter +// should be a non-null pointer, or that all pointer arguments should +// be non-null. +// +// Note: As the GCC manual states, "[s]ince non-static C++ methods +// have an implicit 'this' argument, the arguments of such methods +// should be counted from two, not one." +// +// Args are indexed starting at 1. +// For non-static class member functions, the implicit "this" argument +// is arg 1, and the first explicit argument is arg 2. +// For static class member functions, there is no implicit "this", and +// the first explicit argument is arg 1. +// +// /* arg_a cannot be NULL, but arg_b can */ +// void Function(void* arg_a, void* arg_b) ATTRIBUTE_NONNULL(1); +// +// class C { +// /* arg_a cannot be NULL, but arg_b can */ +// void Method(void* arg_a, void* arg_b) ATTRIBUTE_NONNULL(2); +// +// /* arg_a cannot be NULL, but arg_b can */ +// static void StaticMethod(void* arg_a, void* arg_b) ATTRIBUTE_NONNULL(1); +// }; +// +// If no arguments are provided, then all pointer arguments should be non-null. +// +// /* No pointer arguments may be null. */ +// void Function(void* arg_a, void* arg_b, int arg_c) ATTRIBUTE_NONNULL(); +// +// NOTE: The GCC nonnull attribute actually accepts a list of arguments, but +// ATTRIBUTE_NONNULL does not. +#define ATTRIBUTE_NONNULL(arg_index) __attribute__((nonnull(arg_index))) + +// +// Tell the compiler that a given function never returns +// +#define ATTRIBUTE_NORETURN __attribute__((noreturn)) + +// Tell AddressSanitizer (or other memory testing tools) to ignore a given +// function. Useful for cases when a function reads random locations on stack, +// calls _exit from a cloned subprocess, deliberately accesses buffer +// out of bounds or does other scary things with memory. +#ifdef ADDRESS_SANITIZER +#define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) +#else +#define ATTRIBUTE_NO_SANITIZE_ADDRESS +#endif + +// Tell MemorySanitizer to relax the handling of a given function. All "Use of +// uninitialized value" warnings from such functions will be suppressed, and all +// values loaded from memory will be considered fully initialized. +// This is similar to the ADDRESS_SANITIZER attribute above, but deals with +// initializedness rather than addressability issues. +#ifdef MEMORY_SANITIZER +#define ATTRIBUTE_NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory)) +#else +#define ATTRIBUTE_NO_SANITIZE_MEMORY +#endif + +// Tell ThreadSanitizer to not instrument a given function. +// If you are adding this attribute, please cc dynamic-tools@ on the cl. +#ifdef THREAD_SANITIZER +#define ATTRIBUTE_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread)) +#else +#define ATTRIBUTE_NO_SANITIZE_THREAD +#endif + +// Tell ControlFlowIntegrity sanitizer to not instrument a given function. +#ifdef CONTROL_FLOW_INTEGRITY +#define ATTRIBUTE_NO_SANITIZE_CFI __attribute__((no_sanitize("cfi"))) +#else +#define ATTRIBUTE_NO_SANITIZE_CFI +#endif + +#ifndef HAVE_ATTRIBUTE_SECTION // may have been pre-set to 0, e.g. for Darwin + // // NOLINT +#define HAVE_ATTRIBUTE_SECTION 1 +#endif + +#if HAVE_ATTRIBUTE_SECTION // define section support for the case of GCC + +// +// Tell the compiler/linker to put a given function into a section and define +// "__start_ ## name" and "__stop_ ## name" symbols to bracket the section. +// This functionality is supported by GNU linker. +// Any function with ATTRIBUTE_SECTION must not be inlined, or it will +// be placed into whatever section its caller is placed into. +// +#ifndef ATTRIBUTE_SECTION // NOLINT +#define ATTRIBUTE_SECTION(name) \ + __attribute__((section(#name))) __attribute__((noinline)) +#endif + +// +// Weak section declaration to be used as a global declaration +// for ATTRIBUTE_SECTION_START|STOP(name) to compile and link +// even without functions with ATTRIBUTE_SECTION(name). +// DEFINE_ATTRIBUTE_SECTION should be in the exactly one file; it's +// a no-op on ELF but not on Mach-O. +// +#ifndef DECLARE_ATTRIBUTE_SECTION_VARS // NOLINT +#define DECLARE_ATTRIBUTE_SECTION_VARS(name) \ + extern char __start_##name[] ATTRIBUTE_WEAK; \ + extern char __stop_##name[] ATTRIBUTE_WEAK +#endif +#ifndef DEFINE_ATTRIBUTE_SECTION_VARS // NOLINT +#define INIT_ATTRIBUTE_SECTION_VARS(name) +#define DEFINE_ATTRIBUTE_SECTION_VARS(name) +#endif + +// +// Return void* pointers to start/end of a section of code with +// functions having ATTRIBUTE_SECTION(name). +// Returns 0 if no such functions exits. +// One must DECLARE_ATTRIBUTE_SECTION_VARS(name) for this to compile and link. +// +#define ATTRIBUTE_SECTION_START(name) (reinterpret_cast<void *>(__start_##name)) +#define ATTRIBUTE_SECTION_STOP(name) (reinterpret_cast<void *>(__stop_##name)) + +#endif // HAVE_ATTRIBUTE_SECTION + +// Support for aligning the stack on 32-bit x86. + +#if defined(__i386__) && \ + (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) +#define ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC \ + __attribute__((force_align_arg_pointer)) +#define REQUIRE_STACK_ALIGN_TRAMPOLINE (0) +#elif defined(__i386__) || defined(__x86_64__) +#define REQUIRE_STACK_ALIGN_TRAMPOLINE (1) +#define ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC +#else +#define REQUIRE_STACK_ALIGN_TRAMPOLINE (0) +#define ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC +#endif + +// Tell the compiler to warn about unused return values for functions declared +// with this macro. The macro must appear as the very first part of a function +// declaration or definition: +// +// ABSL_MUST_USE_RESULT Sprocket* AllocateSprocket(); +// +// This placement has the broadest compatibility with GCC, Clang, and MSVC, with +// both defs and decls, and with GCC-style attributes, MSVC declspec, and C++11 +// attributes. Note: past advice was to place the macro after the argument list. +#if defined(SWIG) +#define ABSL_MUST_USE_RESULT +#elif __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) +#define ABSL_MUST_USE_RESULT __attribute__((warn_unused_result)) +#else +#define ABSL_MUST_USE_RESULT +#endif + +// +// Prevent the compiler from padding a structure to natural alignment +// +#if __GNUC__ && !defined(SWIG) +#define ATTRIBUTE_PACKED __attribute__((__packed__)) +#else +#define ATTRIBUTE_PACKED +#endif + +#if defined(COMPILER_GCC3) || defined(__llvm__) +// Defined behavior on some of the uarchs: +// PREFETCH_HINT_T0: +// prefetch to all levels of the hierarchy (except on p4: prefetch to L2) +// PREFETCH_HINT_NTA: +// p4: fetch to L2, but limit to 1 way (out of the 8 ways) +// core: skip L2, go directly to L1 +// k8 rev E and later: skip L2, can go to either of the 2-ways in L1 +enum PrefetchHint { + PREFETCH_HINT_T0 = 3, // More temporal locality + PREFETCH_HINT_T1 = 2, + PREFETCH_HINT_T2 = 1, // Less temporal locality + PREFETCH_HINT_NTA = 0 // No temporal locality +}; +#else +// prefetch is a no-op for this target. Feel free to add more sections above. +#endif + +// The default behavior of prefetch is to speculatively load for read only. This +// is safe for all currently supported platforms. However, prefetch for store +// may have problems depending on the target platform (x86, PPC, arm). Check +// with the platforms team (platforms-servers@) before introducing any changes +// to this function to identify potential impact on current and future servers. +extern inline void prefetch(const void *x, int hint) { +#if defined(__llvm__) + // In the gcc version of prefetch(), hint is only a constant _after_ inlining + // (assumed to have been successful). llvm views things differently, and + // checks constant-ness _before_ inlining. This leads to compilation errors + // with using the other version of this code with llvm. + // + // One way round this is to use a switch statement to explicitly match + // prefetch hint enumerations, and invoke __builtin_prefetch for each valid + // value. llvm's optimization removes the switch and unused case statements + // after inlining, so that this boils down in the end to the same as for gcc; + // that is, a single inlined prefetchX instruction. + // + // Note that this version of prefetch() cannot verify constant-ness of hint. + // If client code calls prefetch() with a variable value for hint, it will + // receive the full expansion of the switch below, perhaps also not inlined. + // This should however not be a problem in the general case of well behaved + // caller code that uses the supplied prefetch hint enumerations. + switch (hint) { + case PREFETCH_HINT_T0: + __builtin_prefetch(x, 0, PREFETCH_HINT_T0); + break; + case PREFETCH_HINT_T1: + __builtin_prefetch(x, 0, PREFETCH_HINT_T1); + break; + case PREFETCH_HINT_T2: + __builtin_prefetch(x, 0, PREFETCH_HINT_T2); + break; + case PREFETCH_HINT_NTA: + __builtin_prefetch(x, 0, PREFETCH_HINT_NTA); + break; + default: + __builtin_prefetch(x); + break; + } +#elif defined(COMPILER_GCC3) + if (__builtin_constant_p(hint)) { + __builtin_prefetch(x, 0, hint); + } else { + // Defaults to PREFETCH_HINT_T0 + __builtin_prefetch(x); + } +#else + // You get no effect. Feel free to add more sections above. +#endif +} + +#ifdef __cplusplus +// prefetch intrinsic (bring data to L1 without polluting L2 cache) +extern inline void prefetch(const void *x) { return prefetch(x, 0); } +#endif // ifdef __cplusplus + +// +// GCC can be told that a certain branch is not likely to be taken (for +// instance, a CHECK failure), and use that information in static analysis. +// Giving it this information can help it optimize for the common case in +// the absence of better information (ie. -fprofile-arcs). +// +#if defined(COMPILER_GCC3) +#define PREDICT_FALSE(x) (__builtin_expect(x, 0)) +#define PREDICT_TRUE(x) (__builtin_expect(!!(x), 1)) +#else +#define PREDICT_FALSE(x) x +#define PREDICT_TRUE(x) x +#endif + +// +// Tell GCC that a function is hot or cold. GCC can use this information to +// improve static analysis, i.e. a conditional branch to a cold function +// is likely to be not-taken. +// This annotation is used for function declarations, e.g.: +// int foo() ATTRIBUTE_HOT; +// +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3) +#define ATTRIBUTE_HOT __attribute__((hot)) +#define ATTRIBUTE_COLD __attribute__((cold)) +#else +#define ATTRIBUTE_HOT +#define ATTRIBUTE_COLD +#endif + +#define FTELLO ftello +#define FSEEKO fseeko + +#else // not GCC + +#define PRINTF_ATTRIBUTE(string_index, first_to_check) +#define SCANF_ATTRIBUTE(string_index, first_to_check) +#define CACHELINE_SIZE 64 +#define CACHELINE_ALIGNED + +#ifndef ATTRIBUTE_UNUSED // NOLINT +#define ATTRIBUTE_UNUSED +#endif // ATTRIBUTE_UNUSED + +#define ATTRIBUTE_ALWAYS_INLINE +#define ATTRIBUTE_NOINLINE +#define ATTRIBUTE_HOT +#define ATTRIBUTE_COLD +#define ATTRIBUTE_WEAK +#define HAVE_ATTRIBUTE_WEAK 0 +#define ATTRIBUTE_INITIAL_EXEC +#define ATTRIBUTE_NONNULL(arg_index) +#define ATTRIBUTE_NORETURN +#define ATTRIBUTE_NO_SANITIZE_ADDRESS +#define ATTRIBUTE_NO_SANITIZE_MEMORY +#define HAVE_ATTRIBUTE_SECTION 0 +#define ATTRIBUTE_PACKED +#define ATTRIBUTE_STACK_ALIGN_FOR_OLD_LIBC +#define REQUIRE_STACK_ALIGN_TRAMPOLINE (0) +#define ABSL_MUST_USE_RESULT +extern inline void prefetch(const void *) {} +#define PREDICT_FALSE(x) x +#define PREDICT_TRUE(x) x + +// These should be redefined appropriately if better alternatives to +// ftell/fseek exist in the compiler +#define FTELLO ftell +#define FSEEKO fseek + +#endif // GCC + +#if ((defined(COMPILER_GCC3) || defined(OS_MACOSX) || defined(OS_IOS) || \ + defined(__NVCC__)) && \ + !defined(SWIG)) || \ + ((__GNUC__ >= 3 || defined(__clang__)) && defined(__ANDROID__)) + +#if !defined(__cplusplus) && !defined(OS_MACOSX) && !defined(OS_IOS) && \ + !defined(OS_CYGWIN) +// stdlib.h only declares this in C++, not in C, so we declare it here. +// Also make sure to avoid declaring it on platforms which don't support it. +extern int posix_memalign(void **memptr, size_t alignment, size_t size); +#endif + +inline void *aligned_malloc(size_t size, int minimum_alignment) { +#if defined(__ANDROID__) || defined(OS_ANDROID) || defined(OS_CYGWIN) + return memalign(minimum_alignment, size); +#else // !__ANDROID__ && !OS_ANDROID && !OS_CYGWIN + void *ptr = NULL; + // posix_memalign requires that the requested alignment be at least + // sizeof(void*). In this case, fall back on malloc which should return memory + // aligned to at least the size of a pointer. + const int required_alignment = sizeof(void *); + if (minimum_alignment < required_alignment) return malloc(size); + if (posix_memalign(&ptr, minimum_alignment, size) != 0) + return NULL; + else + return ptr; +#endif +} + +inline void aligned_free(void *aligned_memory) { free(aligned_memory); } + +#endif +// #if ((defined(COMPILER_GCC3) || defined(OS_MACOSX) || defined(OS_IOS) || +// defined(__NVCC__)) && !defined(SWIG)) || +// ((__GNUC__ >= 3 || defined(__clang__)) && defined(__ANDROID__)) + +// +// Provides a char array with the exact same alignment as another type. The +// first parameter must be a complete type, the second parameter is how many +// of that type to provide space for. +// +// ALIGNED_CHAR_ARRAY(struct stat, 16) storage_; +// +#if defined(__cplusplus) +#undef ALIGNED_CHAR_ARRAY +// Because MSVC and older GCCs require that the argument to their alignment +// construct to be a literal constant integer, we use a template instantiated +// at all the possible powers of two. +#ifndef SWIG // NOLINT +template <int alignment, int size> +struct AlignType {}; +template <int size> +struct AlignType<0, size> { + typedef char result[size]; +}; +#if defined(COMPILER_MSVC) +#define BASE_PORT_H_ALIGN_ATTRIBUTE(X) __declspec(align(X)) +#define BASE_PORT_H_ALIGN_OF(T) __alignof(T) +#elif defined(COMPILER_GCC3) +#define BASE_PORT_H_ALIGN_ATTRIBUTE(X) __attribute__((aligned(X))) +#define BASE_PORT_H_ALIGN_OF(T) __alignof__(T) +#endif + +#if defined(BASE_PORT_H_ALIGN_ATTRIBUTE) + +#define BASE_PORT_H_ALIGNTYPE_TEMPLATE(X) \ + template <int size> \ + struct AlignType<X, size> { \ + typedef BASE_PORT_H_ALIGN_ATTRIBUTE(X) char result[size]; \ + } + +BASE_PORT_H_ALIGNTYPE_TEMPLATE(1); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(2); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(4); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(8); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(16); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(32); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(64); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(128); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(256); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(512); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(1024); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(2048); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(4096); +BASE_PORT_H_ALIGNTYPE_TEMPLATE(8192); +// Any larger and MSVC++ will complain. + +#define ALIGNED_CHAR_ARRAY(T, Size) \ + typename AlignType<BASE_PORT_H_ALIGN_OF(T), sizeof(T) * Size>::result + +#undef BASE_PORT_H_ALIGNTYPE_TEMPLATE +#undef BASE_PORT_H_ALIGN_ATTRIBUTE + +#else // defined(BASE_PORT_H_ALIGN_ATTRIBUTE) +#define ALIGNED_CHAR_ARRAY \ + you_must_define_ALIGNED_CHAR_ARRAY_for_your_compiler_in_base_port_h +#endif // defined(BASE_PORT_H_ALIGN_ATTRIBUTE) + +#else // !SWIG + +// SWIG can't represent alignment and doesn't care about alignment on data +// members (it works fine without it). +template <typename Size> +struct AlignType { + typedef char result[Size]; +}; +#define ALIGNED_CHAR_ARRAY(T, Size) AlignType<Size * sizeof(T)>::result + +#endif // !SWIG +#else // __cpluscplus +#define ALIGNED_CHAR_ARRAY ALIGNED_CHAR_ARRAY_is_not_available_without_Cplusplus +#endif // __cplusplus + +#if !HAVE_ATTRIBUTE_SECTION // provide dummy definitions + +#define ATTRIBUTE_SECTION(name) +#define INIT_ATTRIBUTE_SECTION_VARS(name) +#define DEFINE_ATTRIBUTE_SECTION_VARS(name) +#define DECLARE_ATTRIBUTE_SECTION_VARS(name) +#define ATTRIBUTE_SECTION_START(name) (reinterpret_cast<void *>(0)) +#define ATTRIBUTE_SECTION_STOP(name) (reinterpret_cast<void *>(0)) + +#endif // !HAVE_ATTRIBUTE_SECTION + +#ifdef COMPILER_MSVC /* if Visual C++ */ + +// This compiler flag can be easily overlooked on MSVC. +// _CHAR_UNSIGNED gets set with the /J flag. +#ifndef _CHAR_UNSIGNED // NOLINT +#error chars must be unsigned! Use the /J flag on the compiler command line. +#endif + +// MSVC is a little hyper-active in its warnings +// Signed vs. unsigned comparison is ok. +#pragma warning(disable : 4018) +// We know casting from a long to a char may lose data +#pragma warning(disable : 4244) +// Don't need performance warnings about converting ints to bools +#pragma warning(disable : 4800) +// Integral constant overflow is apparently ok too +// for example: +// short k; int n; +// k = k + n; +#pragma warning(disable : 4307) +// It's ok to use this* in constructor +// Example: +// class C { +// Container cont_; +// C() : cont_(this) { ... +#pragma warning(disable : 4355) +// Truncating from double to float is ok +#pragma warning(disable : 4305) + +#include <assert.h> +#include <process.h> // _getpid() +#include <windows.h> +#include <winsock2.h> +#undef ERROR + +#include <float.h> // for nextafter functionality on windows +#include <math.h> // for HUGE_VAL + +#ifndef HUGE_VALF // NOLINT +#define HUGE_VALF (static_cast<float>(HUGE_VAL)) +#endif + +namespace std {} // namespace std +using namespace std; + +// VC++ doesn't understand "uint" +#ifndef HAVE_UINT // NOLINT +#define HAVE_UINT 1 +typedef unsigned int uint; +#endif + +// VC++ doesn't understand "ssize_t" +// <windows.h> from above includes <BaseTsd.h> and <BaseTsd.h> defines SSIZE_T +#ifndef HAVE_SSIZET // NOLINT +#define HAVE_SSIZET 1 +typedef SSIZE_T ssize_t; +#endif + +#define strtoq _strtoi64 +#define strtouq _strtoui64 +#define strtoll _strtoi64 +#define strtoull _strtoui64 +#define atoll _atoi64 + +// You say tomato, I say atotom +#define PATH_MAX MAX_PATH + +// Wrap Microsoft _snprintf/_vsnprintf calls so they nul-terminate on buffer +// overflow. +#define vsnprintf base_port_MSVC_vsnprintf +inline int base_port_MSVC_vsnprintf(char *str, size_t size, const char *format, + va_list ap) { + int count = _vsnprintf(str, size, format, ap); + if (count < 0) { + count = _vscprintf(format, ap); // Yields character count. + } + if (size > 0 && count >= size) { + str[size - 1] = '\0'; + } + return count; +} + +#define snprintf base_port_MSVC_snprintf +inline int base_port_MSVC_snprintf(char *str, size_t size, const char *fmt, + ...) { + va_list ap; + va_start(ap, fmt); + int count = base_port_MSVC_vsnprintf(str, size, fmt, ap); + va_end(ap); + return count; +} + +// You say tomato, I say _tomato +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#define nextafter _nextafter +#define strdup _strdup +#define tempnam _tempnam +#define chdir _chdir +#define getcwd _getcwd +#define putenv _putenv +#if _MSC_VER >= 1900 // Only needed for VS2015+ +#define getpid _getpid +#define timezone _timezone +#define tzname _tzname +#endif + +// You say tomato, I say toma +inline int random() { return rand(); } +inline void srandom(unsigned int seed) { srand(seed); } + +// You say juxtapose, I say transpose +#define bcopy(s, d, n) memcpy(d, s, n) + +inline void *aligned_malloc(size_t size, int minimum_alignment) { + return _aligned_malloc(size, minimum_alignment); +} + +inline void aligned_free(void *aligned_memory) { + _aligned_free(aligned_memory); +} + +// ----- BEGIN VC++ STUBS & FAKE DEFINITIONS --------------------------------- + +// See http://en.wikipedia.org/wiki/IEEE_754 for details of +// floating point format. + +inline int fpclassify_double(double x) { + const int float_point_class = _fpclass(x); + int c99_class; + switch (float_point_class) { + case _FPCLASS_SNAN: // Signaling NaN + case _FPCLASS_QNAN: // Quiet NaN + c99_class = FP_NAN; + break; + case _FPCLASS_NZ: // Negative zero ( -0) + case _FPCLASS_PZ: // Positive 0 (+0) + c99_class = FP_ZERO; + break; + case _FPCLASS_NINF: // Negative infinity ( -INF) + case _FPCLASS_PINF: // Positive infinity (+INF) + c99_class = FP_INFINITE; + break; + case _FPCLASS_ND: // Negative denormalized + case _FPCLASS_PD: // Positive denormalized + c99_class = FP_SUBNORMAL; + break; + case _FPCLASS_NN: // Negative normalized non-zero + case _FPCLASS_PN: // Positive normalized non-zero + c99_class = FP_NORMAL; + break; + default: + c99_class = FP_NAN; // Should never happen + break; + } + return c99_class; +} + +// This function handle the special subnormal case for float; it will +// become a normal number while casting to double. +// bit_cast is avoided to simplify dependency and to create a code that is +// easy to deploy in C code +inline int fpclassify_float(float x) { + uint32 bitwise_representation; + memcpy(&bitwise_representation, &x, 4); + if ((bitwise_representation & 0x7f800000) == 0 && + (bitwise_representation & 0x007fffff) != 0) + return FP_SUBNORMAL; + return fpclassify_double(x); +} +// +// This define takes care of the denormalized float; the casting to +// double make it a normal number +#define fpclassify(x) \ + ((sizeof(x) == sizeof(float)) ? fpclassify_float(x) : fpclassify_double(x)) + +#define isnan _isnan + +inline int isinf(double x) { + const int float_point_class = _fpclass(x); + if (float_point_class == _FPCLASS_PINF) return 1; + if (float_point_class == _FPCLASS_NINF) return -1; + return 0; +} + +typedef void (*sig_t)(int); + +// This actually belongs in errno.h but there's a name conflict in errno +// on WinNT. They (and a ton more) are also found in Winsock2.h, but +// if'd out under NT. We need this subset at minimum. +#define EXFULL ENOMEM // not really that great a translation... + +// +// Really from <string.h> +// + +inline void bzero(void *s, int n) { memset(s, 0, n); } + +// From glob.h +#define __ptr_t void * + +// Defined all over the place. +typedef int pid_t; + +// From stat.h +typedef unsigned int mode_t; + +// u_int16_t, int16_t don't exist in MSVC +typedef unsigned short u_int16_t; +typedef short int16_t; + +// ----- END VC++ STUBS & FAKE DEFINITIONS ---------------------------------- + +#endif // COMPILER_MSVC + +#ifdef STL_MSVC // not always the same as COMPILER_MSVC +#include "base/port_hash.inc" +#else +struct PortableHashBase {}; +#endif + +#if defined(OS_WINDOWS) || defined(OS_MACOSX) || defined(OS_IOS) +// gethostbyname() *is* thread-safe for Windows native threads. It is also +// safe on Mac OS X and iOS, where it uses thread-local storage, even though the +// manpages claim otherwise. For details, see +// http://lists.apple.com/archives/Darwin-dev/2006/May/msg00008.html +#else +// gethostbyname() is not thread-safe. So disallow its use. People +// should either use the HostLookup::Lookup*() methods, or gethostbyname_r() +#define gethostbyname gethostbyname_is_not_thread_safe_DO_NOT_USE +#endif + +// Define the namespace for pre-C++11 functors for hash_map and hash_set. +// This is not the namespace for C++11 functors (that namespace is "std"). +// +// We used to require that the build tool or Makefile provide this definition. +// Now we usually get it from testing target macros. If the testing target +// macros are different from an external definition, you will get a build +// error. +// + +#if defined(__GNUC__) && defined(GOOGLE_GLIBCXX_VERSION) +// Crosstool v17 or later. +#define HASH_NAMESPACE __gnu_cxx +#elif defined(__GNUC__) && defined(STLPORT) +// A version of gcc with stlport. +#define HASH_NAMESPACE std +#elif defined(_MSC_VER) +// MSVC. +// http://msdn.microsoft.com/en-us/library/6x7w9f6z(v=vs.100).aspx +#define HASH_NAMESPACE stdext +#elif defined(__APPLE__) +// Xcode. +#define HASH_NAMESPACE __gnu_cxx +#elif defined(__GNUC__) +// Some other version of gcc. +#define HASH_NAMESPACE __gnu_cxx +#else +// HASH_NAMESPACE defined externally. +#endif + +#ifndef HASH_NAMESPACE // NOLINT +// I think gcc 2.95.3 was the last toolchain to use this. +#define HASH_NAMESPACE_DECLARATION_START +#define HASH_NAMESPACE_DECLARATION_END +#else +#define HASH_NAMESPACE_DECLARATION_START namespace HASH_NAMESPACE { +#define HASH_NAMESPACE_DECLARATION_END } +#endif + +// Our STL-like classes use __STD. +#if defined(COMPILER_GCC3) || defined(OS_MACOSX) || defined(OS_IOS) || \ + defined(COMPILER_MSVC) +#define __STD std +#endif + +#if defined COMPILER_GCC3 +#define STREAM_SET(s, bit) (s).setstate(ios_base::bit) +#define STREAM_SETF(s, flag) (s).setf(ios_base::flag) +#else +#define STREAM_SET(s, bit) (s).set(ios::bit) +#define STREAM_SETF(s, flag) (s).setf(ios::flag) +#endif + +// Portable handling of unaligned loads, stores, and copies. +// On some platforms, like ARM, the copy functions can be more efficient +// then a load and a store. +// +// It is possible to implement all of these these using constant-length memcpy +// calls, which is portable and will usually be inlined into simple loads and +// stores if the architecture supports it. However, such inlining usually +// happens in a pass that's quite late in compilation, which means the resulting +// loads and stores cannot participate in many other optimizations, leading to +// overall worse code. + +#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \ + defined(MEMORY_SANITIZER) +// Consider we have an unaligned load/store of 4 bytes from address 0x...05. +// AddressSanitizer will treat it as a 3-byte access to the range 05:07 and +// will miss a bug if 08 is the first unaddressable byte. +// ThreadSanitizer will also treat this as a 3-byte access to 05:07 and will +// miss a race between this access and some other accesses to 08. +// MemorySanitizer will correctly propagate the shadow on unaligned stores +// and correctly report bugs on unaligned loads, but it may not properly +// update and report the origin of the uninitialized memory. +// For all three tools, replacing an unaligned access with a tool-specific +// callback solves the problem. + +// Make sure uint16_t/uint32_t/uint64_t are defined. +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus +uint16_t __sanitizer_unaligned_load16(const void *p); +uint32_t __sanitizer_unaligned_load32(const void *p); +uint64_t __sanitizer_unaligned_load64(const void *p); +void __sanitizer_unaligned_store16(void *p, uint16_t v); +void __sanitizer_unaligned_store32(void *p, uint32_t v); +void __sanitizer_unaligned_store64(void *p, uint64_t v); +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +inline uint16 UNALIGNED_LOAD16(const void *p) { + return __sanitizer_unaligned_load16(p); +} + +inline uint32 UNALIGNED_LOAD32(const void *p) { + return __sanitizer_unaligned_load32(p); +} + +inline uint64 UNALIGNED_LOAD64(const void *p) { + return __sanitizer_unaligned_load64(p); +} + +inline void UNALIGNED_STORE16(void *p, uint16 v) { + __sanitizer_unaligned_store16(p, v); +} + +inline void UNALIGNED_STORE32(void *p, uint32 v) { + __sanitizer_unaligned_store32(p, v); +} + +inline void UNALIGNED_STORE64(void *p, uint64 v) { + __sanitizer_unaligned_store64(p, v); +} + +#elif defined(__i386__) || defined(ARCH_K8) || defined(ARCH_PPC) + +// x86 and x86-64 can perform unaligned loads/stores directly; +// modern PowerPC hardware can also do unaligned integer loads and stores; +// but note: the FPU still sends unaligned loads and stores to a trap handler! + +#define UNALIGNED_LOAD16(_p) (*reinterpret_cast<const uint16 *>(_p)) +#define UNALIGNED_LOAD32(_p) (*reinterpret_cast<const uint32 *>(_p)) +#define UNALIGNED_LOAD64(_p) (*reinterpret_cast<const uint64 *>(_p)) + +#define UNALIGNED_STORE16(_p, _val) (*reinterpret_cast<uint16 *>(_p) = (_val)) +#define UNALIGNED_STORE32(_p, _val) (*reinterpret_cast<uint32 *>(_p) = (_val)) +#define UNALIGNED_STORE64(_p, _val) (*reinterpret_cast<uint64 *>(_p) = (_val)) + +#elif defined(__arm__) && !defined(__ARM_ARCH_5__) && \ + !defined(__ARM_ARCH_5T__) && !defined(__ARM_ARCH_5TE__) && \ + !defined(__ARM_ARCH_5TEJ__) && !defined(__ARM_ARCH_6__) && \ + !defined(__ARM_ARCH_6J__) && !defined(__ARM_ARCH_6K__) && \ + !defined(__ARM_ARCH_6Z__) && !defined(__ARM_ARCH_6ZK__) && \ + !defined(__ARM_ARCH_6T2__) + +// ARMv7 and newer support native unaligned accesses, but only of 16-bit +// and 32-bit values (not 64-bit); older versions either raise a fatal signal, +// do an unaligned read and rotate the words around a bit, or do the reads very +// slowly (trip through kernel mode). There's no simple #define that says just +// “ARMv7 or higher”, so we have to filter away all ARMv5 and ARMv6 +// sub-architectures. Newer gcc (>= 4.6) set an __ARM_FEATURE_ALIGNED #define, +// so in time, maybe we can move on to that. +// +// This is a mess, but there's not much we can do about it. +// +// To further complicate matters, only LDR instructions (single reads) are +// allowed to be unaligned, not LDRD (two reads) or LDM (many reads). Unless we +// explicitly tell the compiler that these accesses can be unaligned, it can and +// will combine accesses. On armcc, the way to signal this is done by accessing +// through the type (uint32 __packed *), but GCC has no such attribute +// (it ignores __attribute__((packed)) on individual variables). However, +// we can tell it that a _struct_ is unaligned, which has the same effect, +// so we do that. + +namespace base { +namespace internal { + +struct Unaligned16Struct { + uint16 value; + uint8 dummy; // To make the size non-power-of-two. +} ATTRIBUTE_PACKED; + +struct Unaligned32Struct { + uint32 value; + uint8 dummy; // To make the size non-power-of-two. +} ATTRIBUTE_PACKED; + +} // namespace internal +} // namespace base + +#define UNALIGNED_LOAD16(_p) \ + ((reinterpret_cast<const ::base::internal::Unaligned16Struct *>(_p))->value) +#define UNALIGNED_LOAD32(_p) \ + ((reinterpret_cast<const ::base::internal::Unaligned32Struct *>(_p))->value) + +#define UNALIGNED_STORE16(_p, _val) \ + ((reinterpret_cast< ::base::internal::Unaligned16Struct *>(_p))->value = \ + (_val)) +#define UNALIGNED_STORE32(_p, _val) \ + ((reinterpret_cast< ::base::internal::Unaligned32Struct *>(_p))->value = \ + (_val)) + +// See if that would be more efficient on platforms supporting it, +// at least for copies. + +inline uint64 UNALIGNED_LOAD64(const void *p) { + uint64 t; + memcpy(&t, p, sizeof t); + return t; +} + +inline void UNALIGNED_STORE64(void *p, uint64 v) { memcpy(p, &v, sizeof v); } + +#else + +#define NEED_ALIGNED_LOADS + +// These functions are provided for architectures that don't support +// unaligned loads and stores. + +inline uint16 UNALIGNED_LOAD16(const void *p) { + uint16 t; + memcpy(&t, p, sizeof t); + return t; +} + +inline uint32 UNALIGNED_LOAD32(const void *p) { + uint32 t; + memcpy(&t, p, sizeof t); + return t; +} + +inline uint64 UNALIGNED_LOAD64(const void *p) { + uint64 t; + memcpy(&t, p, sizeof t); + return t; +} + +inline void UNALIGNED_STORE16(void *p, uint16 v) { memcpy(p, &v, sizeof v); } + +inline void UNALIGNED_STORE32(void *p, uint32 v) { memcpy(p, &v, sizeof v); } + +inline void UNALIGNED_STORE64(void *p, uint64 v) { memcpy(p, &v, sizeof v); } + +#endif + +// The UNALIGNED_LOADW and UNALIGNED_STOREW macros load and store values +// of type uword_t. +#ifdef _LP64 +#define UNALIGNED_LOADW(_p) UNALIGNED_LOAD64(_p) +#define UNALIGNED_STOREW(_p, _val) UNALIGNED_STORE64(_p, _val) +#else +#define UNALIGNED_LOADW(_p) UNALIGNED_LOAD32(_p) +#define UNALIGNED_STOREW(_p, _val) UNALIGNED_STORE32(_p, _val) +#endif + +// NOTE(sesse): These are only exported to C++ because the macros they depend on +// use C++-only syntax. This #ifdef can be removed if/when the macros are fixed. + +#if defined(__cplusplus) + +inline void UnalignedCopy16(const void *src, void *dst) { + UNALIGNED_STORE16(dst, UNALIGNED_LOAD16(src)); +} + +inline void UnalignedCopy32(const void *src, void *dst) { + UNALIGNED_STORE32(dst, UNALIGNED_LOAD32(src)); +} + +inline void UnalignedCopy64(const void *src, void *dst) { + if (sizeof(void *) == 8) { + UNALIGNED_STORE64(dst, UNALIGNED_LOAD64(src)); + } else { + const char *src_char = reinterpret_cast<const char *>(src); + char *dst_char = reinterpret_cast<char *>(dst); + + UNALIGNED_STORE32(dst_char, UNALIGNED_LOAD32(src_char)); + UNALIGNED_STORE32(dst_char + 4, UNALIGNED_LOAD32(src_char + 4)); + } +} + +#endif // defined(__cpluscplus) + +// printf macros for size_t, in the style of inttypes.h +#if defined(_LP64) || defined(OS_IOS) +#define __PRIS_PREFIX "z" +#else +#define __PRIS_PREFIX +#endif + +// Use these macros after a % in a printf format string +// to get correct 32/64 bit behavior, like this: +// size_t size = records.size(); +// printf("%" PRIuS "\n", size); + +#define PRIdS __PRIS_PREFIX "d" +#define PRIxS __PRIS_PREFIX "x" +#define PRIuS __PRIS_PREFIX "u" +#define PRIXS __PRIS_PREFIX "X" +#define PRIoS __PRIS_PREFIX "o" + +#define GPRIuPTHREAD "lu" +#define GPRIxPTHREAD "lx" +#ifdef OS_CYGWIN +#define PRINTABLE_PTHREAD(pthreadt) reinterpret_cast<uintptr_t>(pthreadt) +#else +#define PRINTABLE_PTHREAD(pthreadt) pthreadt +#endif + +#define DDEPTH_SIZEOF_MEMBER(t, f) sizeof(((t *)4096)->f) + +#define OFFSETOF_MEMBER(t, f) \ + (reinterpret_cast<char *>(&reinterpret_cast<t *>(16)->f) - \ + reinterpret_cast<char *>(16)) + +#ifdef PTHREADS_REDHAT_WIN32 +#include <pthread.h> // NOLINT(build/include) +#include <iosfwd> // NOLINT(build/include) +// pthread_t is not a simple integer or pointer on Win32 +std::ostream &operator<<(std::ostream &out, const pthread_t &thread_id); +#endif + +// GXX_EXPERIMENTAL_CXX0X is defined by gcc and clang up to at least +// gcc-4.7 and clang-3.1 (2011-12-13). __cplusplus was defined to 1 +// in gcc before 4.7 (Crosstool 16) and clang before 3.1, but is +// defined according to the language version in effect thereafter. +// Microsoft Visual Studio 14 (2015) sets __cplusplus==199711 despite +// reasonably good C++11 support, so we set LANG_CXX for it and +// newer versions (_MSC_VER >= 1900). Stlport is used by many Android +// projects and does not have full C++11 STL support. +#if (defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L || \ + (defined(_MSC_VER) && _MSC_VER >= 1900)) && \ + !defined(STLPORT) +// Define this to 1 if the code is compiled in C++11 mode; leave it +// undefined otherwise. Do NOT define it to 0 -- that causes +// '#ifdef LANG_CXX11' to behave differently from '#if LANG_CXX11'. +#define LANG_CXX11 1 +#endif + +// On some platforms, a "function pointer" points to a function descriptor +// rather than directly to the function itself. Use FUNC_PTR_TO_CHAR_PTR(func) +// to get a char-pointer to the first instruction of the function func. +#if (defined(__powerpc__) && !(_CALL_ELF > 1)) || defined(__ia64) +// use opd section for function descriptors on these platforms, the function +// address is the first word of the descriptor +enum { kPlatformUsesOPDSections = 1 }; +#define FUNC_PTR_TO_CHAR_PTR(func) (reinterpret_cast<char **>(func)[0]) +#else +enum { kPlatformUsesOPDSections = 0 }; +#define FUNC_PTR_TO_CHAR_PTR(func) (reinterpret_cast<char *>(func)) +#endif + +// Private implementation detail: __has_extension is useful to implement +// static_assert, and defining it for all toolchains avoids an extra level of +// nesting of #if/#ifdef/#ifndef. +#ifndef __has_extension // NOLINT +#define __has_extension(x) 0 // MSVC 10's preprocessor can't handle 'false'. +#endif + +#ifdef __cplusplus +// We support C++11's static_assert(expression, message) for all C++ +// builds, though for some pre-C++11 toolchains we fall back to using +// GG_PRIVATE_STATIC_ASSERT, which has two limitations: (1) the +// expression argument will need to be parenthesized if it would +// otherwise contain commas outside of parentheses, and (2) the +// message is ignored (though the compiler error will likely mention +// "static_assert_failed" and point to the line with the failing assertion). + +// Something else (perhaps libc++) may have provided its own definition of +// static_assert. +#ifndef static_assert // NOLINT +#if LANG_CXX11 || __has_extension(cxx_static_assert) || defined(_MSC_VER) +// There's a native implementation of static_assert, no need to define our own. +#elif __has_extension(c_static_assert) +// C11's _Static_assert is available, and makes a great static_assert. +#define static_assert _Static_assert +#else +// Fall back on our home-grown implementation, with its limitations. +#define static_assert GG_PRIVATE_STATIC_ASSERT +#endif +#endif + +// CompileAssert is an implementation detail of COMPILE_ASSERT and +// GG_PRIVATE_STATIC_ASSERT. +template <bool> +struct CompileAssert {}; + +// GG_PRIVATE_STATIC_ASSERT: A poor man's static_assert. This doesn't handle +// condition expressions that contain unparenthesized top-level commas; +// write GG_PRIVATE_STATIC_ASSERT((expr), "comment") when needed. +#define GG_PRIVATE_CAT_IMMEDIATE(a, b) a##b +#define GG_PRIVATE_CAT(a, b) GG_PRIVATE_CAT_IMMEDIATE(a, b) +#define GG_PRIVATE_STATIC_ASSERT(expr, ignored) \ + typedef CompileAssert<(static_cast<bool>(expr))> GG_PRIVATE_CAT( \ + static_assert_failed_at_line, \ + __LINE__)[bool(expr) ? 1 : -1] DDEPTH_ATTRIBUTE_UNUSED // NOLINT + +#endif // __cplusplus + +// Some platforms have a ::string class that is different from ::std::string +// (although the interface is the same, of course). On other platforms, +// ::string is the same as ::std::string. +#if defined(__cplusplus) && !defined(SWIG) +#include <string> +#ifndef HAS_GLOBAL_STRING // NOLINT +using std::basic_string; +using std::string; +#endif // HAS_GLOBAL_STRING +#endif // SWIG, __cplusplus + +#endif // DYNAMIC_DEPTH_INTERNAL_BASE_PORT_H_ // NOLINT diff --git a/internal/base/reenable_warnings.h b/internal/base/reenable_warnings.h new file mode 100644 index 0000000..9351c4d --- /dev/null +++ b/internal/base/reenable_warnings.h @@ -0,0 +1,9 @@ +// This is not your usual header guard. See disable_warnings.h +#ifdef DYNAMIC_DEPTH_WARNINGS_DISABLED +#undef DYNAMIC_DEPTH_WARNINGS_DISABLED + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // DYNAMIC_DEPTH_WARNINGS_DISABLED diff --git a/internal/dynamic_depth/README.md b/internal/dynamic_depth/README.md new file mode 100644 index 0000000..906b3a5 --- /dev/null +++ b/internal/dynamic_depth/README.md @@ -0,0 +1,8 @@ +# dynamic\_depth - A library for parsing and writing Dynamic Depth metadata + +dynamic\_depth is a portable library for parsing and writing Dynamic Depth +metadata, developed at Google. + +The Dynamic Depth specification is a standard for storing device-related +metadata in common image containers such as JPEG and PNG, while maintaining +compatibility with existing image viewers. diff --git a/internal/dynamic_depth/app_info.cc b/internal/dynamic_depth/app_info.cc new file mode 100644 index 0000000..d0c4f1c --- /dev/null +++ b/internal/dynamic_depth/app_info.cc @@ -0,0 +1,145 @@ +#include "dynamic_depth/app_info.h" + +#include <memory> + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "strings/numbers.h" +#include "xmpmeta/base64.h" +#include "xmpmeta/xml/utils.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kPropertyPrefix[] = "AppInfo"; +const char kVersion[] = "Version"; +const char kApplication[] = "Application"; +const char kItemUri[] = "ItemURI"; + +const char kTextMime[] = "text/plain"; + +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/appinfo/"; + +} // namespace + +// Private constructor. +AppInfo::AppInfo() : application_(""), version_(""), item_uri_("") {} + +// Public methods. +void AppInfo::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->insert( + std::pair<string, string>(kPropertyPrefix, kNamespaceHref)); +} + +std::unique_ptr<AppInfo> AppInfo::FromData( + const string& application, const string& version, const string& data, + const string& item_uri, std::vector<std::unique_ptr<Item>>* items) { + if (application.empty()) { + LOG(ERROR) << "No application name given"; + return nullptr; + } + + if (version.empty() && item_uri.empty() && items == nullptr) { + LOG(ERROR) << "One of version or item_uri must be present, but neither was " + << "found, or items is null while version is empty"; + return nullptr; + } + + if (!item_uri.empty() && items == nullptr) { + LOG(ERROR) << "Item URI given, but no place to store the generated item " + "element; returning null"; + return nullptr; + } + + if (!item_uri.empty() && data.empty()) { + LOG(ERROR) << "Data provided, but no item URI given"; + return nullptr; + } + + // Store the data with a text/plain mimetype. + if (!data.empty() && !item_uri.empty() && items != nullptr) { + ItemParams item_params(kTextMime, data.size(), item_uri); + item_params.payload_to_serialize = data; + items->emplace_back(Item::FromData(item_params)); + } + + std::unique_ptr<AppInfo> + vendor_info(std::unique_ptr<AppInfo>(new AppInfo())); // NOLINT + vendor_info->application_ = application; + vendor_info->version_ = version; + vendor_info->item_uri_ = item_uri; + return vendor_info; +} + +std::unique_ptr<AppInfo> AppInfo::FromDeserializer( + const Deserializer& parent_deserializer, const string& namespace_str) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer(namespace_str, kPropertyPrefix); + if (deserializer == nullptr) { + return nullptr; + } + + std::unique_ptr<AppInfo> + vendor_info(std::unique_ptr<AppInfo>(new AppInfo())); // NOLINT + if (!vendor_info->ParseFields(*deserializer)) { + return nullptr; + } + return vendor_info; +} + +const string& AppInfo::GetApplication() const { return application_; } +const string& AppInfo::GetVersion() const { return version_; } +const string& AppInfo::GetItemUri() const { return item_uri_; } + +bool AppInfo::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + // Write required field. + if (!serializer->WriteProperty(DynamicDepthConst::AppInfo(), kApplication, + application_)) { + return false; + } + + // No error checking here, because we've already done that in the instantiator + // and deserializer. + if (!version_.empty()) { + serializer->WriteProperty(DynamicDepthConst::AppInfo(), kVersion, version_); + } + + if (!item_uri_.empty()) { + serializer->WriteProperty(DynamicDepthConst::AppInfo(), kItemUri, + item_uri_); + } + return true; +} + +// Private methods. +bool AppInfo::ParseFields(const Deserializer& deserializer) { + // Required field. + if (!deserializer.ParseString(DynamicDepthConst::AppInfo(), kApplication, + &application_)) { + return false; + } + + // One of the following fields must be present. + bool success = deserializer.ParseString(DynamicDepthConst::AppInfo(), + kVersion, &version_); + success |= deserializer.ParseString(DynamicDepthConst::AppInfo(), kItemUri, + &item_uri_); + return success; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/camera.cc b/internal/dynamic_depth/camera.cc new file mode 100644 index 0000000..db068e3 --- /dev/null +++ b/internal/dynamic_depth/camera.cc @@ -0,0 +1,282 @@ + +#include "dynamic_depth/camera.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/camera/"; + +constexpr const char* kTrait = "Trait"; +constexpr const char* kTraitPhysical = "Physical"; +constexpr const char* kTraitPhysicalLower = "physical"; +constexpr const char* kTraitLogical = "Logical"; +constexpr const char* kTraitLogicalLower = "logical"; + +constexpr const char* kImageJpegMime = "image/jpeg"; + +string TraitToString(CameraTrait trait) { + switch (trait) { + case PHYSICAL: + return kTraitPhysical; + case LOGICAL: + return kTraitLogical; + case NONE: // Fallthrough. + default: + return ""; + } +} + +CameraTrait StringToTrait(const string& trait_name) { + string trait_lower = trait_name; + std::transform(trait_lower.begin(), trait_lower.end(), trait_lower.begin(), + ::tolower); + if (kTraitPhysicalLower == trait_lower) { + return CameraTrait::PHYSICAL; + } + + if (kTraitLogicalLower == trait_lower) { + return CameraTrait::LOGICAL; + } + + return CameraTrait::NONE; +} + +std::unique_ptr<Camera> ParseFields(const Deserializer& deserializer) { + string trait_str; + deserializer.ParseString(DynamicDepthConst::Camera(), kTrait, &trait_str); + CameraTrait trait = StringToTrait(trait_str); + + std::unique_ptr<Image> image = Image::FromDeserializer(deserializer); + if (image == nullptr) { + LOG(ERROR) << "An image must be present in a Camera, but none was found"; + return nullptr; + } + + std::unique_ptr<LightEstimate> light_estimate = + LightEstimate::FromDeserializer(deserializer); + + std::unique_ptr<Pose> pose = + Pose::FromDeserializer(deserializer, DynamicDepthConst::Camera()); + + std::unique_ptr<DepthMap> depth_map = + DepthMap::FromDeserializer(deserializer); + + std::unique_ptr<ImagingModel> imaging_model = + ImagingModel::FromDeserializer(deserializer); + + std::unique_ptr<PointCloud> point_cloud = + PointCloud::FromDeserializer(deserializer); + + std::unique_ptr<VendorInfo> vendor_info = + VendorInfo::FromDeserializer(deserializer, DynamicDepthConst::Camera()); + + std::unique_ptr<AppInfo> app_info = + AppInfo::FromDeserializer(deserializer, DynamicDepthConst::Camera()); + + std::unique_ptr<CameraParams> params(new CameraParams(std::move(image))); + params->depth_map = std::move(depth_map); + params->light_estimate = std::move(light_estimate); + params->pose = std::move(pose); + params->imaging_model = std::move(imaging_model); + params->point_cloud = std::move(point_cloud); + params->vendor_info = std::move(vendor_info); + params->app_info = std::move(app_info); + params->trait = trait; + return Camera::FromData(std::move(params)); +} + +} // namespace + +// Private constructor. +Camera::Camera(std::unique_ptr<CameraParams> params) { + params_ = std::move(params); +} + +// Public methods. +void Camera::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::Camera(), kNamespaceHref); + if (params_->image) { + params_->image->GetNamespaces(ns_name_href_map); + } + if (params_->light_estimate) { + params_->light_estimate->GetNamespaces(ns_name_href_map); + } + if (params_->pose) { + params_->pose->GetNamespaces(ns_name_href_map); + } + if (params_->depth_map) { + params_->depth_map->GetNamespaces(ns_name_href_map); + } + if (params_->imaging_model) { + params_->imaging_model->GetNamespaces(ns_name_href_map); + } + if (params_->point_cloud) { + params_->point_cloud->GetNamespaces(ns_name_href_map); + } + if (params_->vendor_info) { + params_->vendor_info->GetNamespaces(ns_name_href_map); + } + if (params_->app_info) { + params_->app_info->GetNamespaces(ns_name_href_map); + } +} + +std::unique_ptr<Camera> Camera::FromDataForCamera0( + std::unique_ptr<CameraParams> params, + std::vector<std::unique_ptr<Item>>* items) { + if (params->image == nullptr) { + params->image = Image::FromDataForPrimaryImage(kImageJpegMime, items); + } + return std::unique_ptr<Camera>(new Camera(std::move(params))); // NOLINT +} + +std::unique_ptr<Camera> Camera::FromData(std::unique_ptr<CameraParams> params) { + if (params->image == nullptr) { + LOG(ERROR) << "Camera must have an image eleemnt"; + return nullptr; + } + + return std::unique_ptr<Camera>(new Camera(std::move(params))); // NOLINT +} + +std::unique_ptr<Camera> Camera::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Camera()), + DynamicDepthConst::Camera()); + if (deserializer == nullptr) { + return nullptr; + } + + return ParseFields(*deserializer); +} + +const Image* Camera::GetImage() const { return params_->image.get(); } + +const LightEstimate* Camera::GetLightEstimate() const { + return params_->light_estimate.get(); +} + +const Pose* Camera::GetPose() const { return params_->pose.get(); } + +const DepthMap* Camera::GetDepthMap() const { return params_->depth_map.get(); } + +const ImagingModel* Camera::GetImagingModel() const { + return params_->imaging_model.get(); +} + +const PointCloud* Camera::GetPointCloud() const { + return params_->point_cloud.get(); +} + +const VendorInfo* Camera::GetVendorInfo() const { + return params_->vendor_info.get(); +} + +const AppInfo* Camera::GetAppInfo() const { return params_->app_info.get(); } + +CameraTrait Camera::GetTrait() const { return params_->trait; } + +bool Camera::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (params_->trait != CameraTrait::NONE) { + string trait_name = TraitToString(params_->trait); + serializer->WriteProperty(DynamicDepthConst::Camera(), kTrait, trait_name); + } + + // Error checking has already been done at instantiation time. + if (params_->image != nullptr) { + std::unique_ptr<Serializer> image_serializer = serializer->CreateSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Image()), + DynamicDepthConst::Image()); + if (!params_->image->Serialize(image_serializer.get())) { + LOG(WARNING) << "Could not serialize Image"; + } + } + + if (params_->depth_map != nullptr) { + std::unique_ptr<Serializer> depth_map_serializer = + serializer->CreateSerializer(DynamicDepthConst::Camera(), + DynamicDepthConst::DepthMap()); + if (!params_->depth_map->Serialize(depth_map_serializer.get())) { + LOG(WARNING) << "Could not serializer Depth Map"; + } + } + + if (params_->light_estimate != nullptr) { + std::unique_ptr<Serializer> light_estimate_serializer = + serializer->CreateSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::LightEstimate()), + DynamicDepthConst::LightEstimate()); + if (!params_->light_estimate->Serialize(light_estimate_serializer.get())) { + LOG(WARNING) << "Could not serialize LightEstimate"; + } + } + + if (params_->pose != nullptr) { + std::unique_ptr<Serializer> pose_serializer = serializer->CreateSerializer( + DynamicDepthConst::Camera(), DynamicDepthConst::Pose()); + if (!params_->pose->Serialize(pose_serializer.get())) { + LOG(WARNING) << "Could not serialize Pose"; + } + } + + if (params_->imaging_model != nullptr) { + std::unique_ptr<Serializer> imaging_model_serializer = + serializer->CreateSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::ImagingModel()), + DynamicDepthConst::ImagingModel()); + if (!params_->imaging_model->Serialize(imaging_model_serializer.get())) { + LOG(WARNING) << "Could not serialize ImagingModel"; + } + } + + if (params_->point_cloud != nullptr) { + std::unique_ptr<Serializer> point_cloud_serializer = + serializer->CreateSerializer(DynamicDepthConst::Camera(), + DynamicDepthConst::PointCloud()); + if (!params_->point_cloud->Serialize(point_cloud_serializer.get())) { + LOG(WARNING) << "Could not serialize PointCloud"; + } + } + + if (params_->vendor_info != nullptr) { + std::unique_ptr<Serializer> vendor_info_serializer = + serializer->CreateSerializer(DynamicDepthConst::Camera(), + DynamicDepthConst::VendorInfo()); + if (!params_->vendor_info->Serialize(vendor_info_serializer.get())) { + LOG(WARNING) << "Could not serialize VendorInfo"; + } + } + + if (params_->app_info != nullptr) { + std::unique_ptr<Serializer> app_info_serializer = + serializer->CreateSerializer(DynamicDepthConst::Camera(), + DynamicDepthConst::AppInfo()); + if (!params_->app_info->Serialize(app_info_serializer.get())) { + LOG(WARNING) << "Could not serialize AppInfo"; + } + } + + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/cameras.cc b/internal/dynamic_depth/cameras.cc new file mode 100644 index 0000000..7c5c29f --- /dev/null +++ b/internal/dynamic_depth/cameras.cc @@ -0,0 +1,102 @@ +#include "dynamic_depth/cameras.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { + +const char kNodeName[] = "Cameras"; +const char kCameraName[] = "Camera"; + +// Private constructor. +Cameras::Cameras() {} + +// Public methods. +void Cameras::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr || camera_list_.empty()) { + LOG(ERROR) << "Namespace list is null or camera list is empty"; + return; + } + for (const auto& camera : camera_list_) { + camera->GetNamespaces(ns_name_href_map); + } +} + +std::unique_ptr<Cameras> Cameras::FromCameraArray( + std::vector<std::unique_ptr<Camera>>* camera_list) { + if (camera_list == nullptr || camera_list->empty()) { + LOG(ERROR) << "Camera list is empty"; + return nullptr; + } + std::unique_ptr<Cameras> cameras(new Cameras()); + cameras->camera_list_ = std::move(*camera_list); + return cameras; +} + +std::unique_ptr<Cameras> Cameras::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Cameras> cameras(new Cameras()); + int i = 0; + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(kNodeName), kNodeName, 0); + while (deserializer) { + std::unique_ptr<Camera> camera = Camera::FromDeserializer(*deserializer); + if (camera == nullptr) { + LOG(ERROR) << "Unable to deserialize a camera"; + return nullptr; + } + cameras->camera_list_.emplace_back(std::move(camera)); + deserializer = parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(kNodeName), kNodeName, ++i); + } + + if (cameras->camera_list_.empty()) { + return nullptr; + } + return cameras; +} + +const std::vector<const Camera*> Cameras::GetCameras() const { + std::vector<const Camera*> camera_list; + for (const auto& camera : camera_list_) { + camera_list.push_back(camera.get()); + } + return camera_list; +} + +bool Cameras::Serialize(Serializer* serializer) const { + if (camera_list_.empty()) { + LOG(ERROR) << "Camera list is empty"; + return false; + } + std::unique_ptr<Serializer> cameras_serializer = + serializer->CreateListSerializer(DynamicDepthConst::Namespace(kNodeName), + kNodeName); + if (cameras_serializer == nullptr) { + // Error is logged in Serializer. + return false; + } + for (int i = 0; i < camera_list_.size(); i++) { + std::unique_ptr<Serializer> camera_serializer = + cameras_serializer->CreateItemSerializer( + DynamicDepthConst::Namespace(kCameraName), kCameraName); + if (camera_serializer == nullptr) { + LOG(ERROR) << "Could not create a list item serializer for Camera"; + return false; + } + if (!camera_list_[i]->Serialize(camera_serializer.get())) { + LOG(ERROR) << "Could not serialize camera " << i; + return false; + } + } + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/const.cc b/internal/dynamic_depth/const.cc new file mode 100644 index 0000000..0a7396d --- /dev/null +++ b/internal/dynamic_depth/const.cc @@ -0,0 +1,114 @@ +#include "dynamic_depth/const.h" + +#include "android-base/logging.h" +#include "base/port.h" + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +// Element names. +constexpr char kAppInfo[] = "AppInfo"; +constexpr char kCamera[] = "Camera"; +constexpr char kDepthMap[] = "DepthMap"; +constexpr char kDevice[] = "Device"; +constexpr char kEarthPose[] = "EarthPose"; +constexpr char kImagingModel[] = "ImagingModel"; +constexpr char kImage[] = "Image"; +constexpr char kItem[] = "Item"; +constexpr char kLightEstimate[] = "LightEstimate"; +constexpr char kPlane[] = "Plane"; +constexpr char kPointCloud[] = "PointCloud"; +constexpr char kPose[] = "Pose"; +constexpr char kProfile[] = "Profile"; +constexpr char kVendorInfo[] = "VendorInfo"; + +// Type names. +constexpr char kCameras[] = "Cameras"; +constexpr char kContainer[] = "Container"; +constexpr char kPlanes[] = "Planes"; +constexpr char kProfiles[] = "Profiles"; + +} // namespace + +// Redeclare static constexpr variables. +// https://stackoverflow.com/questions/8016780/ +// undefined-reference-to-static-constexpr-char +constexpr std::array<const char*, DynamicDepthConst::kNumDistortionTypes> + DynamicDepthConst::kDistortionTypeNames; + +// Dynamic Depth element names. +const char* DynamicDepthConst::AppInfo() { return kAppInfo; } + +const char* DynamicDepthConst::Camera() { return kCamera; } + +const char* DynamicDepthConst::DepthMap() { return kDepthMap; } + +const char* DynamicDepthConst::Device() { return kDevice; } + +const char* DynamicDepthConst::EarthPose() { return kEarthPose; } + +const char* DynamicDepthConst::ImagingModel() { return kImagingModel; } + +const char* DynamicDepthConst::Image() { return kImage; } + +const char* DynamicDepthConst::Item() { return kItem; } + +const char* DynamicDepthConst::LightEstimate() { return kLightEstimate; } + +const char* DynamicDepthConst::Plane() { return kPlane; } + +const char* DynamicDepthConst::PointCloud() { return kPointCloud; } + +const char* DynamicDepthConst::Pose() { return kPose; } + +const char* DynamicDepthConst::Profile() { return kProfile; } + +const char* DynamicDepthConst::VendorInfo() { return kVendorInfo; } + +// Dynamic Depth type names. +const char* DynamicDepthConst::Cameras() { return kCameras; } + +const char* DynamicDepthConst::Container() { return kContainer; } + +const char* DynamicDepthConst::Planes() { return kPlanes; } + +const char* DynamicDepthConst::Profiles() { return kProfiles; } + +// Returns the namespace to which the given Dynamic Depth element or type +// belongs. AppInfo and VendorInfo are not included because they can belong to +// either the Device or Camera elements. +const std::string DynamicDepthConst::Namespace(const std::string& node_name) { + if (node_name == kPose) { + LOG(WARNING) << kPose << " maps to " << kDevice << ", " << kCamera + << ", and " << kPlane << "; should be manually chosen. " + << "Returning empty"; + return ""; + } + + // Elements. + if (node_name == kImagingModel || node_name == kImage || + node_name == kDepthMap || node_name == kPointCloud || + node_name == kLightEstimate) { + return kCamera; + } + + if (node_name == kItem) { + return kContainer; + } + + if (node_name == kCamera || node_name == kEarthPose || + node_name == kProfile || node_name == kPlane) { + return kDevice; + } + + // Types. + if (node_name == kCameras || node_name == kContainer || + node_name == kPlanes || node_name == kProfiles) { + return kDevice; + } + + return ""; +} +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/const.h b/internal/dynamic_depth/const.h new file mode 100644 index 0000000..9b56abd --- /dev/null +++ b/internal/dynamic_depth/const.h @@ -0,0 +1,51 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_CONST_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_CONST_H_ // NOLINT + +#include <array> +#include <map> +#include <string> +#include <vector> + +namespace photos_editing_formats { +namespace dynamic_depth { + +struct DynamicDepthConst { + // Dynamic Depth element names. + static const char* AppInfo(); + static const char* Camera(); + static const char* DepthMap(); + static const char* Device(); + static const char* EarthPose(); + static const char* ImagingModel(); + static const char* LightEstimate(); + static const char* Image(); + static const char* Item(); + static const char* Plane(); + static const char* PointCloud(); + static const char* Pose(); + static const char* Profile(); + static const char* VendorInfo(); + + // Dynamic Depth type names (not shared with elements). + static const char* Cameras(); + static const char* Container(); + static const char* Planes(); + static const char* Profiles(); + + // Maps elements to the names of their XML namespaces. + static const std::string Namespace(const std::string& node_name); + + // Distortion type names. + // LINT.IfChange + static constexpr int kNumDistortionTypes = 4; + static constexpr std::array<const char*, kNumDistortionTypes> + kDistortionTypeNames = { + {"None", "BrownsTwoParams", "BrownsThreeParams", "BrownsFiveParams"}}; + // LINT.ThenChange(//depot/google3/photos/editing/formats/dynamic_depth/\ + // internal/dynamic_depth/distortion_type.h) +}; + +} // namespace dynamic_depth +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_CONST_H_ // NOLINT diff --git a/internal/dynamic_depth/container.cc b/internal/dynamic_depth/container.cc new file mode 100644 index 0000000..1e3b9b6 --- /dev/null +++ b/internal/dynamic_depth/container.cc @@ -0,0 +1,122 @@ +#include "dynamic_depth/container.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { + +constexpr char kNamespaceHref[] = + "http://ns.google.com/photos/dd/1.0/container/"; +constexpr char kDirectory[] = "Directory"; + +// Private constructor. +Container::Container() {} + +void Container::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr || items_.empty()) { + LOG(ERROR) << "Namespace list is null or item list is empty"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::Container(), kNamespaceHref); + items_[0]->GetNamespaces(ns_name_href_map); +} + +std::unique_ptr<Container> Container::FromItems( + std::vector<std::unique_ptr<Item>>* items) { + if (items == nullptr || items->empty()) { + LOG(ERROR) << "Item list is empty"; + return nullptr; + } + + std::unique_ptr<Container> container(new Container()); + container->items_ = std::move(*items); + // Purge item elements that are null. + container->items_.erase( + std::remove_if( + container->items_.begin(), container->items_.end(), + [](const std::unique_ptr<Item>& item) { return item == nullptr; }), + container->items_.end()); + if (container->items_.empty()) { + LOG(ERROR) << "No non-null elements in items"; + return nullptr; + } + + return container; +} + +std::unique_ptr<Container> Container::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Container> container(new Container()); + int i = 0; + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(DynamicDepthConst::Container()), + DynamicDepthConst::Container(), 0); + while (deserializer) { + std::unique_ptr<Item> item = Item::FromDeserializer(*deserializer); + if (item == nullptr) { + LOG(ERROR) << "Unable to deserialize a item"; + return nullptr; + } + container->items_.emplace_back(std::move(item)); + deserializer = parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(DynamicDepthConst::Container()), + DynamicDepthConst::Container(), ++i); + } + + if (container->items_.empty()) { + return nullptr; + } + return container; +} + +const std::vector<const Item*> Container::GetItems() const { + std::vector<const Item*> items; + for (const auto& item : items_) { + items.push_back(item.get()); + } + return items; +} + +bool Container::Serialize(Serializer* serializer) const { + if (items_.empty()) { + LOG(ERROR) << "Item list is empty"; + return false; + } + + std::unique_ptr<Serializer> container_serializer = + serializer->CreateSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Container()), + DynamicDepthConst::Container()); + std::unique_ptr<Serializer> directory_serializer = + container_serializer->CreateListSerializer(DynamicDepthConst::Container(), + kDirectory); + if (directory_serializer == nullptr) { + // Error is logged in Serializer. + return false; + } + + for (int i = 0; i < items_.size(); i++) { + std::unique_ptr<Serializer> item_serializer = + directory_serializer->CreateItemSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Item()), + DynamicDepthConst::Item()); + if (item_serializer == nullptr) { + LOG(ERROR) << "Could not create a list item serializer for Item"; + return false; + } + if (!items_[i]->Serialize(item_serializer.get())) { + LOG(ERROR) << "Could not serialize item " << i; + return false; + } + } + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/depth_map.cc b/internal/dynamic_depth/depth_map.cc new file mode 100644 index 0000000..4d16c8d --- /dev/null +++ b/internal/dynamic_depth/depth_map.cc @@ -0,0 +1,362 @@ +#include "dynamic_depth/depth_map.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "dynamic_depth/item.h" +#include "strings/numbers.h" +#include "xmpmeta/base64.h" + +using photos_editing_formats::dynamic_depth::Item; +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { +constexpr const char* kNamespaceHref = + "http://ns.google.com/photos/dd/1.0/depthmap/"; + +constexpr const char* kFormat = "Format"; +constexpr const char* kNear = "Near"; +constexpr const char* kFar = "Far"; +constexpr const char* kUnits = "Units"; +constexpr const char* kDepthUri = "DepthURI"; +constexpr const char* kItemSemantic = "ItemSemantic"; +constexpr const char* kConfidenceUri = "ConfidenceURI"; +constexpr const char* kMeasureType = "MeasureType"; +constexpr const char* kSoftware = "Software"; +constexpr const char* kFocalTable = "FocalTable"; +constexpr const char* kFocalTableEntryCount = "FocalTableEntryCount"; + +constexpr const char* kFormatRangeInverse = "RangeInverse"; +constexpr const char* kFormatRangeLinear = "RangeLinear"; +constexpr const char* kFormatRangeInverseLower = "rangeinverse"; +constexpr const char* kFormatRangeLinearLower = "rangelinear"; + +constexpr const char* kUnitsMeters = "Meters"; +constexpr const char* kUnitsDiopters = "Diopters"; +constexpr const char* kUnitsNone = "None"; +constexpr const char* kUnitsMetersLower = "meters"; +constexpr const char* kUnitsDioptersLower = "diopters"; + +constexpr const char* kMeasureTypeOpticalAxis = "OpticalAxis"; +constexpr const char* kMeasureTypeOpticRay = "OpticRay"; +constexpr const char* kMeasureTypeOpticRayLower = "opticray"; + +constexpr const char* kItemSemanticDepth = "Depth"; +constexpr const char* kItemSemanticSegmentation = "Segmentation"; +constexpr const char* kItemSemanticSegmentationLower = "segmentation"; + +string ItemSemanticToString(DepthItemSemantic item_semantic) { + switch (item_semantic) { + case DepthItemSemantic::kDepth: + return kItemSemanticDepth; + case DepthItemSemantic::kSegmentation: + return kItemSemanticSegmentation; + } +} + +DepthItemSemantic StringToItemSemantic(const string& semantic_str) { + string semantic_str_lower = semantic_str; + std::transform(semantic_str_lower.begin(), semantic_str_lower.end(), + semantic_str_lower.begin(), ::tolower); + if (kItemSemanticSegmentationLower == semantic_str_lower) { + return DepthItemSemantic::kSegmentation; + } + + return DepthItemSemantic::kDepth; +} + +string FormatToString(DepthFormat format) { + switch (format) { + case DepthFormat::kRangeInverse: + return kFormatRangeInverse; + case DepthFormat::kRangeLinear: + return kFormatRangeLinear; + case DepthFormat::kFormatNone: + return ""; + } +} + +// Case insensitive. +DepthFormat StringToFormat(const string& format_str) { + string format_str_lower = format_str; + std::transform(format_str_lower.begin(), format_str_lower.end(), + format_str_lower.begin(), ::tolower); + if (kFormatRangeInverseLower == format_str_lower) { + return DepthFormat::kRangeInverse; + } + + if (kFormatRangeLinearLower == format_str_lower) { + return DepthFormat::kRangeLinear; + } + + return DepthFormat::kFormatNone; +} + +string UnitsToString(DepthUnits units) { + switch (units) { + case DepthUnits::kMeters: + return kUnitsMeters; + case DepthUnits::kDiopters: + return kUnitsDiopters; + case DepthUnits::kUnitsNone: + return kUnitsNone; + } +} + +DepthUnits StringToUnits(const string& units_str) { + string units_str_lower = units_str; + std::transform(units_str_lower.begin(), units_str_lower.end(), + units_str_lower.begin(), ::tolower); + if (kUnitsMetersLower == units_str_lower) { + return DepthUnits::kMeters; + } + + if (kUnitsDioptersLower == units_str_lower) { + return DepthUnits::kDiopters; + } + + return DepthUnits::kUnitsNone; +} + +string MeasureTypeToString(DepthMeasureType measure_type) { + switch (measure_type) { + case DepthMeasureType::kOpticRay: + return kMeasureTypeOpticRay; + case DepthMeasureType::kOpticalAxis: + return kMeasureTypeOpticalAxis; + } +} + +DepthMeasureType StringToMeasureType(const string& measure_type_str) { + string measure_type_str_lower = measure_type_str; + std::transform(measure_type_str_lower.begin(), measure_type_str_lower.end(), + measure_type_str_lower.begin(), ::tolower); + if (kMeasureTypeOpticRayLower == measure_type_str_lower) { + return DepthMeasureType::kOpticRay; + } + + return DepthMeasureType::kOpticalAxis; +} + +} // namespace + +// Private constructor. +DepthMap::DepthMap(const DepthMapParams& params) : params_(params) {} + +// Private parser. +std::unique_ptr<DepthMap> DepthMap::ParseFields( + const Deserializer& deserializer) { + const string& prefix = DynamicDepthConst::DepthMap(); + string format_str; + float near; + float far; + string units_str; + string depth_uri; + string item_semantic_str; + + if (!deserializer.ParseString(prefix, kItemSemantic, &item_semantic_str) || + !deserializer.ParseString(prefix, kFormat, &format_str) || + !deserializer.ParseFloat(prefix, kNear, &near) || + !deserializer.ParseFloat(prefix, kFar, &far) || + !deserializer.ParseString(prefix, kUnits, &units_str) || + !deserializer.ParseString(prefix, kDepthUri, &depth_uri)) { + return nullptr; + } + + DepthMapParams params(StringToFormat(format_str), near, far, + StringToUnits(units_str), depth_uri); + params.item_semantic = StringToItemSemantic(item_semantic_str); + + string confidence_uri; + if (deserializer.ParseString(prefix, kConfidenceUri, &confidence_uri)) { + params.confidence_uri = confidence_uri; + } + + string measure_type_str; + if (deserializer.ParseString(prefix, kMeasureType, &measure_type_str)) { + params.measure_type = StringToMeasureType(measure_type_str); + } + + string software; + if (deserializer.ParseString(prefix, kSoftware, &software)) { + params.software = software; + } + + std::vector<float> focal_table; + int focal_table_entry_count; + if (deserializer.ParseFloatArrayBase64(prefix, kFocalTable, &focal_table) && + (!deserializer.ParseInt(prefix, kFocalTableEntryCount, + &focal_table_entry_count) && + focal_table.size() / 2 != focal_table_entry_count)) { + return nullptr; + } + params.focal_table = focal_table; + + return std::unique_ptr<DepthMap>(new DepthMap(params)); // NOLINT +} + +// Public methods. +void DepthMap::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::DepthMap(), kNamespaceHref); +} + +std::unique_ptr<DepthMap> DepthMap::FromData( + const DepthMapParams& params, std::vector<std::unique_ptr<Item>>* items) { + if (params.format == DepthFormat::kFormatNone) { + LOG(ERROR) + << "Format must be specified, cannot be of type DepthFormat::NONE"; + return nullptr; + } + + if (params.depth_uri.empty() || params.depth_image_data.empty()) { + LOG(ERROR) << "Depth image data and URI must be provided"; + return nullptr; + } + + if (!params.focal_table.empty() && params.focal_table.size() % 2 != 0) { + LOG(ERROR) << "Focal table entries must consist of pairs"; + return nullptr; + } + + if (items == nullptr) { + LOG(ERROR) << "List of items is null"; + return nullptr; + } + + if (params.mime.empty()) { + LOG(ERROR) << "Depth image mime must be provided to DepthMapParams"; + return nullptr; + } + + ItemParams depth_item_params(params.mime, params.depth_image_data.size(), + params.depth_uri); + depth_item_params.payload_to_serialize = params.depth_image_data; + items->emplace_back(Item::FromData(depth_item_params)); + + bool available_confidence_uri_and_data = true; + if (!params.confidence_uri.empty() && !params.confidence_data.empty()) { + // Assumes that the confidence mime is the same as that of the depth map. + ItemParams confidence_item_params( + params.mime, params.confidence_data.size(), params.confidence_uri); + confidence_item_params.payload_to_serialize = params.confidence_data; + items->emplace_back(Item::FromData(confidence_item_params)); + } else if (!params.confidence_uri.empty() && params.confidence_data.empty()) { + LOG(ERROR) << "No confidence data provided, the URI will be set to empty " + "and not serialized"; + available_confidence_uri_and_data = false; + } + + auto depth_map = std::unique_ptr<DepthMap>(new DepthMap(params)); // NOLINT + if (!available_confidence_uri_and_data) { + // Ensure we don't serialize the confidence URI if no data has been + // provided. + depth_map->params_.confidence_uri = ""; + } + + return depth_map; +} + +std::unique_ptr<DepthMap> DepthMap::FromDeserializer( + const xml::Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::DepthMap()), + DynamicDepthConst::DepthMap()); + if (deserializer == nullptr) { + LOG(ERROR) << "Deserializer must not be null"; + return nullptr; + } + + return ParseFields(*deserializer); +} + +DepthFormat DepthMap::GetFormat() const { return params_.format; } +float DepthMap::GetNear() const { return params_.near; } +float DepthMap::GetFar() const { return params_.far; } +DepthUnits DepthMap::GetUnits() const { return params_.units; } +const string DepthMap::GetDepthUri() const { return params_.depth_uri; } +DepthItemSemantic DepthMap::GetItemSemantic() const { + return params_.item_semantic; +} +const string DepthMap::GetConfidenceUri() const { + return params_.confidence_uri; +} + +DepthMeasureType DepthMap::GetMeasureType() const { + return params_.measure_type; +} + +const string DepthMap::GetSoftware() const { return params_.software; } +const std::vector<float>& DepthMap::GetFocalTable() const { + return params_.focal_table; +} + +size_t DepthMap::GetFocalTableEntryCount() const { + return params_.focal_table.size() / 2; +} + +bool DepthMap::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + if (params_.depth_uri.empty()) { + LOG(ERROR) << "Depth image URI is empty"; + return false; + } + + const string& prefix = DynamicDepthConst::DepthMap(); + // Error checking is already done in FromData. + if (!serializer->WriteProperty(prefix, kItemSemantic, + ItemSemanticToString(params_.item_semantic)) || + !serializer->WriteProperty(prefix, kFormat, + FormatToString(params_.format)) || + !serializer->WriteProperty(prefix, kUnits, + UnitsToString(params_.units)) || + !serializer->WriteProperty(prefix, kNear, std::to_string(params_.near)) || + !serializer->WriteProperty(prefix, kFar, std::to_string(params_.far)) || + !serializer->WriteProperty(prefix, kDepthUri, params_.depth_uri)) { + return false; + } + + serializer->WriteProperty(prefix, kMeasureType, + MeasureTypeToString(params_.measure_type)); + + if (!params_.confidence_uri.empty()) { + serializer->WriteProperty(prefix, kConfidenceUri, params_.confidence_uri); + } + + if (!params_.software.empty()) { + serializer->WriteProperty(prefix, kSoftware, params_.software); + } + + if (!params_.focal_table.empty()) { + string base64_encoded_focal_table; + if (!EncodeFloatArrayBase64(params_.focal_table, + &base64_encoded_focal_table)) { + LOG(ERROR) << "Focal table encoding failed"; + } else { + int focal_table_entry_count = + static_cast<int>(params_.focal_table.size() / 2); + if (!serializer->WriteProperty( + prefix, kFocalTableEntryCount, + ::dynamic_depth::strings::SimpleItoa(focal_table_entry_count)) || + !serializer->WriteProperty(prefix, kFocalTable, + base64_encoded_focal_table)) { + LOG(ERROR) << "Focal table or entry count could not be serialized"; + } + } + } + + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/device.cc b/internal/dynamic_depth/device.cc new file mode 100644 index 0000000..2cfff99 --- /dev/null +++ b/internal/dynamic_depth/device.cc @@ -0,0 +1,313 @@ +#include "dynamic_depth/device.h" + +#include <libxml/tree.h> + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "dynamic_depth/vendor_info.h" +#include "xmpmeta/xml/const.h" +#include "xmpmeta/xml/deserializer_impl.h" +#include "xmpmeta/xml/search.h" +#include "xmpmeta/xml/serializer_impl.h" +#include "xmpmeta/xml/utils.h" +#include "xmpmeta/xmp_data.h" +#include "xmpmeta/xmp_parser.h" +#include "xmpmeta/xmp_writer.h" + +using photos_editing_formats::xml::DepthFirstSearch; +using photos_editing_formats::xml::DeserializerImpl; +using photos_editing_formats::xml::GetFirstDescriptionElement; +using photos_editing_formats::xml::Serializer; +using photos_editing_formats::xml::SerializerImpl; +using photos_editing_formats::xml::ToXmlChar; +using photos_editing_formats::xml::XmlConst; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kRevision[] = "Revision"; +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/device/"; + +// Parses Device fields and children elements from xmlDocPtr. +std::unique_ptr<Device> ParseFields(const xmlDocPtr& xmlDoc) { + // Find and parse the Device node. + // Only these two fields are required to be present; the rest are optional. + // TODO(miraleung): Search for Device by namespace. + xmlNodePtr device_node = + DepthFirstSearch(xmlDoc, DynamicDepthConst::Device()); + if (device_node == nullptr) { + LOG(ERROR) << "No device node found"; + return nullptr; + } + + const DeserializerImpl deserializer(device_node); + auto cameras = Cameras::FromDeserializer(deserializer); + if (cameras == nullptr) { + LOG(ERROR) << "No cameras found"; + return nullptr; + } + + auto container = Container::FromDeserializer(deserializer); + + // The list of cameras is now guaranteed to have at least one element, because + // of implementation in cameras.cc + auto planes = Planes::FromDeserializer(deserializer); + auto earth_pose = EarthPose::FromDeserializer(deserializer); + auto pose = Pose::FromDeserializer(deserializer, DynamicDepthConst::Device()); + auto profiles = Profiles::FromDeserializer(deserializer); + auto vendor_info = + VendorInfo::FromDeserializer(deserializer, DynamicDepthConst::Device()); + auto app_info = + AppInfo::FromDeserializer(deserializer, DynamicDepthConst::Device()); + + std::unique_ptr<DeviceParams> + params(new DeviceParams(std::move(cameras))); // NOLINT + params->container = std::move(container); + params->planes = std::move(planes); + params->earth_pose = std::move(earth_pose); + params->pose = std::move(pose); + params->profiles = std::move(profiles); + params->vendor_info = std::move(vendor_info); + params->app_info = std::move(app_info); + return Device::FromData(std::move(params)); +} + +// Parses Device fields and children elements from XmpData. +std::unique_ptr<Device> ParseFields(const XmpData& xmp) { + if (xmp.ExtendedSection() == nullptr) { + LOG(ERROR) << "XMP extended section is null"; + return nullptr; + } + + return ParseFields(xmp.ExtendedSection()); +} + +} // namespace + +// Private constructor. +Device::Device(std::unique_ptr<DeviceParams> params) { + params_ = std::move(params); +} + +// Public methods. +std::unique_ptr<Device> Device::FromData(std::unique_ptr<DeviceParams> params) { + if (params->cameras == nullptr) { + LOG(ERROR) << "At least one camera must be provided"; + return nullptr; + } + + // The list of cameras is now guaranteed to have at least one element, because + // of the implementation in cameras.cc + return std::unique_ptr<Device>(new Device(std::move(params))); // NOLINT +} + +std::unique_ptr<Device> Device::FromXmp(const XmpData& xmp) { + return ParseFields(xmp); +} + +std::unique_ptr<Device> Device::FromJpegFile(const string& filename) { + XmpData xmp; + const bool kSkipExtended = false; + if (!ReadXmpHeader(filename, kSkipExtended, &xmp)) { + return nullptr; + } + return FromXmp(xmp); +} + +// Creates a Device by parsing XML file containing the metadata. +std::unique_ptr<Device> Device::FromXmlFile(const string& filename) { + xmlDocPtr xmlDoc = xmlReadFile(filename.c_str(), nullptr, 0); + if (xmlDoc == nullptr) { + LOG(ERROR) << "Failed to read file: " << filename; + return nullptr; + } + + auto device = ParseFields(xmlDoc); + xmlFreeDoc(xmlDoc); + return device; +} + +const Cameras* Device::GetCameras() const { return params_->cameras.get(); } + +const Container* Device::GetContainer() const { + return params_->container.get(); +} + +const EarthPose* Device::GetEarthPose() const { + return params_->earth_pose.get(); +} + +const Pose* Device::GetPose() const { return params_->pose.get(); } + +const Planes* Device::GetPlanes() const { return params_->planes.get(); } + +const Profiles* Device::GetProfiles() const { return params_->profiles.get(); } + +const VendorInfo* Device::GetVendorInfo() const { + return params_->vendor_info.get(); +} + +const AppInfo* Device::GetAppInfo() const { return params_->app_info.get(); } + +// This cannot be const because of memory management for the namespaces. +// namespaces_ are freed when the XML document(s) in xmp are freed. +// If namespaces_ are populated at object creation time and this +// object is serialized, freeing the xmlNs objects in the destructor will result +// memory management errors. +bool Device::SerializeToXmp(XmpData* xmp) { + if (xmp == nullptr || xmp->StandardSection() == nullptr || + xmp->ExtendedSection() == nullptr) { + LOG(ERROR) << "XmpData or its sections are null"; + return false; + } + return Serialize(xmp->MutableExtendedSection()); +} + +bool Device::SerializeToXmlFile(const char* filename) { + std::unique_ptr<XmpData> xmp_data = CreateXmpData(true); + if (!Serialize(xmp_data->MutableExtendedSection())) { + return false; + } + return xmlSaveFile(filename, xmp_data->ExtendedSection()) != -1; +} + +// Private methods. +bool Device::Serialize(xmlDocPtr* xmlDoc) { + xmlNodePtr root_node = GetFirstDescriptionElement(*xmlDoc); + if (root_node == nullptr) { + LOG(ERROR) << "Extended section has no rdf:Description node"; + return false; + } + + if (params_->cameras == nullptr) { + LOG(ERROR) << "At least one camera must be present, stopping serialization"; + return false; + } + + // Create a node here instead of through a new deserializer, otherwise + // an extraneous prefix will be written to the node name. + xmlNodePtr device_node = + xmlNewNode(nullptr, ToXmlChar(DynamicDepthConst::Device())); + xmlAddChild(root_node, device_node); + + PopulateNamespaces(); + xmlNsPtr prev_ns = root_node->ns; + for (const auto& entry : namespaces_) { + if (prev_ns != nullptr) { + prev_ns->next = entry.second; + } + prev_ns = entry.second; + } + + // Set up serialization on the first description node in the extended section. + SerializerImpl device_serializer(namespaces_, device_node); + + // Serialize elements. + if (params_->container && + !params_->container->Serialize(&device_serializer)) { + return false; + } + + if (params_->earth_pose) { + std::unique_ptr<Serializer> earth_pose_serializer = + device_serializer.CreateSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::EarthPose()), + DynamicDepthConst::EarthPose()); + if (!params_->earth_pose->Serialize(earth_pose_serializer.get())) { + return false; + } + } + + if (params_->pose) { + std::unique_ptr<Serializer> pose_serializer = + device_serializer.CreateSerializer(DynamicDepthConst::Device(), + DynamicDepthConst::Pose()); + if (!params_->pose->Serialize(pose_serializer.get())) { + return false; + } + } + + if (params_->profiles && !params_->profiles->Serialize(&device_serializer)) { + return false; + } + + // Serialize Planes before Cameras, since the data in Planes is likely to be + // significantly smaller than the potential media types in a Camera. + if (params_->planes && !params_->planes->Serialize(&device_serializer)) { + return false; + } + + if (params_->cameras && !params_->cameras->Serialize(&device_serializer)) { + return false; + } + + if (params_->vendor_info) { + std::unique_ptr<Serializer> vendor_info_serializer = + device_serializer.CreateSerializer(DynamicDepthConst::Device(), + DynamicDepthConst::VendorInfo()); + if (!params_->vendor_info->Serialize(vendor_info_serializer.get())) { + return false; + } + } + + if (params_->app_info) { + std::unique_ptr<Serializer> app_info_serializer = + device_serializer.CreateSerializer(DynamicDepthConst::Device(), + DynamicDepthConst::AppInfo()); + if (!params_->app_info->Serialize(app_info_serializer.get())) { + return false; + } + } + + return true; +} +void Device::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) const { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list is null"; + return; + } + ns_name_href_map->emplace(XmlConst::RdfPrefix(), XmlConst::RdfNodeNs()); + ns_name_href_map->emplace(DynamicDepthConst::Device(), kNamespaceHref); + if (params_->earth_pose) { + params_->earth_pose->GetNamespaces(ns_name_href_map); + } + if (params_->pose) { + params_->pose->GetNamespaces(ns_name_href_map); + } + if (params_->profiles) { + params_->profiles->GetNamespaces(ns_name_href_map); + } + if (params_->planes) { + params_->planes->GetNamespaces(ns_name_href_map); + } + if (params_->cameras) { + params_->cameras->GetNamespaces(ns_name_href_map); + } + if (params_->container) { + params_->container->GetNamespaces(ns_name_href_map); + } + if (params_->vendor_info) { + params_->vendor_info->GetNamespaces(ns_name_href_map); + } + if (params_->app_info) { + params_->app_info->GetNamespaces(ns_name_href_map); + } +} + +// Gathers all the XML namespaces of child elements. +void Device::PopulateNamespaces() { + std::unordered_map<string, string> ns_name_href_map; + GetNamespaces(&ns_name_href_map); + for (const auto& entry : ns_name_href_map) { + if (!namespaces_.count(entry.first)) { + namespaces_.emplace(entry.first, + xmlNewNs(nullptr, ToXmlChar(entry.second.data()), + ToXmlChar(entry.first.data()))); + } + } +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/dimension.h b/internal/dynamic_depth/dimension.h new file mode 100644 index 0000000..9f68f54 --- /dev/null +++ b/internal/dynamic_depth/dimension.h @@ -0,0 +1,26 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_DIMENSION_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_DIMENSION_H_ // NOLINT + +namespace photos_editing_formats { +namespace dynamic_depth { + +// A struct that contains the width and height of a size or the x and y +// coordinates of a point. +struct Dimension { + Dimension(int w, int h) : width(w), height(h) {} + int width; + int height; + + inline bool operator==(const Dimension& other) const { + return width == other.width && height == other.height; + } + + inline bool operator!=(const Dimension& other) const { + return !(*this == other); + } +}; + +} // namespace dynamic_depth +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_DIMENSION_H_ // NOLINT diff --git a/internal/dynamic_depth/dynamic_depth.cc b/internal/dynamic_depth/dynamic_depth.cc new file mode 100644 index 0000000..3b2b114 --- /dev/null +++ b/internal/dynamic_depth/dynamic_depth.cc @@ -0,0 +1,125 @@ +#include "dynamic_depth/dynamic_depth.h" + +#include <fstream> +#include <sstream> + +#include "android-base/logging.h" +#include "dynamic_depth/container.h" +#include "dynamic_depth/item.h" +#include "image_io/gcontainer/gcontainer.h" +#include "xmpmeta/xmp_data.h" +#include "xmpmeta/xmp_writer.h" + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +using photos_editing_formats::CreateXmpData; +using photos_editing_formats::XmpData; + +constexpr char kImageMimePrefix[] = "image"; + +bool IsMimeTypeImage(const string& mime) { + string mime_lower = mime; + std::transform(mime_lower.begin(), mime_lower.end(), mime_lower.begin(), + ::tolower); + return strncmp(mime_lower.c_str(), kImageMimePrefix, mime_lower.find("/")) == + 0; +} + +} // namespace + +bool WriteImageAndMetadataAndContainer(const string& out_filename, + const uint8_t* primary_image_bytes, + size_t primary_image_bytes_count, + Device* device) { + const std::unique_ptr<XmpData> xmp_data = CreateXmpData(true); + device->SerializeToXmp(xmp_data.get()); + std::istringstream input_jpeg_stream( + std::string(reinterpret_cast<const char*>(primary_image_bytes), + primary_image_bytes_count)); + std::ofstream output_jpeg_stream; + output_jpeg_stream.open(out_filename, std::ostream::out); + bool success = WriteLeftEyeAndXmpMeta( + out_filename, *xmp_data, &input_jpeg_stream, &output_jpeg_stream); + + if (device->GetContainer() == nullptr) { + output_jpeg_stream.close(); + return success; + } + + // Append Container:Item elements' payloads. + for (auto item : device->GetContainer()->GetItems()) { + const string& payload = item->GetPayloadToSerialize(); + const unsigned int payload_size = item->GetLength(); + if (payload_size <= 0 || payload.empty()) { + continue; + } + output_jpeg_stream.write(payload.c_str(), payload_size); + } + + output_jpeg_stream.close(); + return success; +} + +bool GetItemPayload(const string& input_image_filename, const Device* device, + const string& item_uri, string* out_payload) { + if (device == nullptr || device->GetContainer() == nullptr) { + LOG(ERROR) << "No Container element to parse"; + return false; + } + + return GetItemPayload(input_image_filename, device->GetContainer(), item_uri, + out_payload); +} + +bool GetItemPayload(const string& input_image_filename, + const Container* container, const string& item_uri, + string* out_payload) { + if (container == nullptr) { + LOG(ERROR) << "Container cannot be null"; + return false; + } + + size_t file_offset = 0; + size_t file_length = 0; + int index = 0; + bool is_mime_type_image = false; + for (const auto& item : container->GetItems()) { + is_mime_type_image = IsMimeTypeImage(item->GetMime()); + + if (item_uri.compare(item->GetDataUri()) == 0) { + // Found a matching item. + file_length = item->GetLength(); + break; + } + + file_offset += item->GetLength(); + index++; + } + + if (file_length == 0) { + if (index == 0 && is_mime_type_image) { + LOG(INFO) << "Item references the primary image, Not populating data"; + return true; + } + + // File length can be zero to indicate the primary image (checked above) or + // use the last file in the list. If this check fails, it's not in this + // state. + if (file_offset == 0) { + LOG(ERROR) << "Not using the primary image, or not image mime, or not " + "the first item, but has file offset of 0"; + return false; + } + } + + std::string std_payload; + bool success = image_io::gcontainer::ParseFileAfterImage( + input_image_filename, file_offset, file_length, &std_payload); + *out_payload = std_payload; + return success; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/earth_pose.cc b/internal/dynamic_depth/earth_pose.cc new file mode 100644 index 0000000..a57aa68 --- /dev/null +++ b/internal/dynamic_depth/earth_pose.cc @@ -0,0 +1,190 @@ +#include "dynamic_depth/earth_pose.h" + +#include <math.h> + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kLatitude[] = "Latitude"; +const char kLongitude[] = "Longitude"; +const char kAltitude[] = "Altitude"; +const char kRotationX[] = "RotationX"; +const char kRotationY[] = "RotationY"; +const char kRotationZ[] = "RotationZ"; +const char kRotationW[] = "RotationW"; +const char kTimestamp[] = "Timestamp"; +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/earthpose/"; + +const std::vector<float> NormalizeQuaternion(const std::vector<float>& quat) { + if (quat.size() < 4) { + return std::vector<float>(); + } + float length = + sqrt((quat[0] * quat[0]) + (quat[1] * quat[1]) + (quat[2] * quat[2])) + + (quat[3] * quat[3]); + const std::vector<float> normalized = {quat[0] / length, quat[1] / length, + quat[2] / length, quat[3] / length}; + return normalized; +} + +} // namespace + +// Private constructor. +EarthPose::EarthPose() : timestamp_(-1) {} + +// Public methods. +void EarthPose::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::EarthPose(), kNamespaceHref); +} + +std::unique_ptr<EarthPose> EarthPose::FromData( + const std::vector<double>& position, const std::vector<float>& orientation, + const int64 timestamp) { + if (position.empty() && orientation.empty()) { + LOG(ERROR) << "Either position or orientation must be provided"; + return nullptr; + } + + std::unique_ptr<EarthPose> earth_pose(new EarthPose()); + if (position.size() >= 3) { + earth_pose->position_ = position; + } + + if (orientation.size() >= 4) { + earth_pose->orientation_ = NormalizeQuaternion(orientation); + } + + if (timestamp >= 0) { + earth_pose->timestamp_ = timestamp; + } + + return earth_pose; +} + +std::unique_ptr<EarthPose> EarthPose::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::EarthPose()), + DynamicDepthConst::EarthPose()); + if (deserializer == nullptr) { + return nullptr; + } + std::unique_ptr<EarthPose> earth_pose(new EarthPose()); + if (!earth_pose->ParseEarthPoseFields(*deserializer)) { + return nullptr; + } + return earth_pose; +} + +bool EarthPose::HasPosition() const { return position_.size() == 3; } +bool EarthPose::HasOrientation() const { return orientation_.size() == 4; } + +const std::vector<double>& EarthPose::GetPosition() const { return position_; } + +const std::vector<float>& EarthPose::GetOrientation() const { + return orientation_; +} + +int64 EarthPose::GetTimestamp() const { return timestamp_; } + +bool EarthPose::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (!HasPosition() && !HasOrientation()) { + LOG(ERROR) << "Device pose has neither position nor orientation"; + return false; + } + + bool success = true; + if (position_.size() == 3) { + success &= + serializer->WriteProperty(DynamicDepthConst::EarthPose(), kLatitude, + std::to_string(position_[0])) && + serializer->WriteProperty(DynamicDepthConst::EarthPose(), kLongitude, + std::to_string(position_[1])) && + serializer->WriteProperty(DynamicDepthConst::EarthPose(), kAltitude, + std::to_string(position_[2])); + } + + if (orientation_.size() == 4) { + success &= + serializer->WriteProperty(DynamicDepthConst::EarthPose(), kRotationX, + std::to_string(orientation_[0])) && + serializer->WriteProperty(DynamicDepthConst::EarthPose(), kRotationY, + std::to_string(orientation_[1])) && + serializer->WriteProperty(DynamicDepthConst::EarthPose(), kRotationZ, + std::to_string(orientation_[2])) && + serializer->WriteProperty(DynamicDepthConst::EarthPose(), kRotationW, + std::to_string(orientation_[3])); + } + + if (timestamp_ >= 0) { + serializer->WriteProperty(DynamicDepthConst::EarthPose(), kTimestamp, + std::to_string(timestamp_)); + } + + return success; +} + +// Private methods. +bool EarthPose::ParseEarthPoseFields(const Deserializer& deserializer) { + double lat, lon, alt; + // If a position field is present, the rest must be as well. + if (deserializer.ParseDouble(DynamicDepthConst::EarthPose(), kLatitude, + &lat)) { + if (!deserializer.ParseDouble(DynamicDepthConst::EarthPose(), kLongitude, + &lon)) { + return false; + } + if (!deserializer.ParseDouble(DynamicDepthConst::EarthPose(), kAltitude, + &alt)) { + return false; + } + position_ = {lat, lon, alt}; + } + + // Same for orientation. + float x, y, z, w; + if (deserializer.ParseFloat(DynamicDepthConst::EarthPose(), kRotationX, &x)) { + if (!deserializer.ParseFloat(DynamicDepthConst::EarthPose(), kRotationY, + &y)) { + return false; + } + if (!deserializer.ParseFloat(DynamicDepthConst::EarthPose(), kRotationZ, + &z)) { + return false; + } + if (!deserializer.ParseFloat(DynamicDepthConst::EarthPose(), kRotationW, + &w)) { + return false; + } + orientation_ = std::vector<float>({x, y, z, w}); + } + + if (position_.size() < 3 && orientation_.size() < 4) { + return false; + } + + deserializer.ParseLong(DynamicDepthConst::EarthPose(), kTimestamp, + ×tamp_); + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/element.h b/internal/dynamic_depth/element.h new file mode 100644 index 0000000..e68829d --- /dev/null +++ b/internal/dynamic_depth/element.h @@ -0,0 +1,34 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_ELEMENT_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_ELEMENT_H_ // NOLINT + +#include <unordered_map> + +#include "xmpmeta/xml/deserializer.h" +#include "xmpmeta/xml/serializer.h" + +namespace photos_editing_formats { +namespace dynamic_depth { + +/** + * An interface for an element in the Dynamic Depth spec. + */ +class Element { + public: + virtual ~Element() {} + + // Appends child elements' namespaces' and their respective hrefs to the + // given collection, and any parent nodes' names to prefix_names. + // Key: Name of the namespace. + // Value: Full namespace URL. + // Example: ("Image", "http://ns.google.com/photos/dd/1.0/image/") + virtual void GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) = 0; + + // Serializes this element. + virtual bool Serialize(xml::Serializer* serializer) const = 0; +}; + +} // namespace dynamic_depth +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_ELEMENT_H_ // NOLINT diff --git a/internal/dynamic_depth/image.cc b/internal/dynamic_depth/image.cc new file mode 100644 index 0000000..811932e --- /dev/null +++ b/internal/dynamic_depth/image.cc @@ -0,0 +1,193 @@ +#include "dynamic_depth/image.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "dynamic_depth/item.h" + +using photos_editing_formats::dynamic_depth::Item; +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +constexpr char kItemUri[] = "ItemURI"; +constexpr char kItemSemantic[] = "ItemSemantic"; + +constexpr char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/image/"; +constexpr char kPrimaryImagePlaceholderItemUri[] = "primary_image"; + +constexpr char kItemSemanticPrimary[] = "Primary"; +constexpr char kItemSemanticOriginal[] = "Original"; +constexpr char kItemSemanticPrimaryLower[] = "primary"; + +string ItemSemanticToString(ImageItemSemantic item_semantic) { + switch (item_semantic) { + case ImageItemSemantic::kPrimary: + return kItemSemanticPrimary; + case ImageItemSemantic::kOriginal: + return kItemSemanticOriginal; + } +} + +ImageItemSemantic StringToItemSemantic(const string& item_semantic_str) { + string item_semantic_str_lower = item_semantic_str; + std::transform(item_semantic_str_lower.begin(), item_semantic_str_lower.end(), + item_semantic_str_lower.begin(), ::tolower); + if (kItemSemanticPrimaryLower == item_semantic_str_lower) { + return ImageItemSemantic::kPrimary; + } + + // Don't fail, default to Original. + return ImageItemSemantic::kOriginal; +} + +} // namespace + +// Private constructor. +Image::Image() {} + +// Public methods. +void Image::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::Image(), kNamespaceHref); +} + +std::unique_ptr<Image> Image::FromData( + const string& data, const string& mime, const string& item_uri, + std::vector<std::unique_ptr<Item>>* items) { + if (data.empty() || mime.empty()) { + LOG(ERROR) << "No image data or mimetype given"; + return nullptr; + } + + if (item_uri.empty()) { + LOG(ERROR) << "Item URI must be provided"; + return nullptr; + } + + if (items == nullptr) { + LOG(ERROR) << "List of items is null"; + return nullptr; + } + + ItemParams item_params(mime, data.size(), item_uri); + item_params.payload_to_serialize = data; + items->emplace_back(Item::FromData(item_params)); + + std::unique_ptr<Image> image(std::unique_ptr<Image>(new Image())); // NOLINT + image->item_uri_ = item_uri; + image->item_semantic_ = ImageItemSemantic::kOriginal; + return image; +} + +std::unique_ptr<Image> Image::FromDataForPrimaryImage( + const string& mime, std::vector<std::unique_ptr<Item>>* items) { + if (mime.empty()) { + LOG(ERROR) << "No mimetype given"; + return nullptr; + } + + if (items == nullptr) { + LOG(ERROR) << "List of items is null"; + return nullptr; + } + + ItemParams item_params(mime, 0, kPrimaryImagePlaceholderItemUri); + items->emplace_back(Item::FromData(item_params)); + + std::unique_ptr<Image> image(std::unique_ptr<Image>(new Image())); // NOLINT + image->item_uri_ = kPrimaryImagePlaceholderItemUri; + image->item_semantic_ = ImageItemSemantic::kPrimary; + return image; +} + +std::unique_ptr<Image> Image::FromData( + const uint8_t* data, size_t data_size, const string& mime, + const string& item_uri, std::vector<std::unique_ptr<Item>>* items) { + if ((data == nullptr || data_size == 0) || mime.empty()) { + LOG(ERROR) << "No image data or mimetype given"; + return nullptr; + } + + if (item_uri.empty()) { + LOG(ERROR) << "Item URI must be provided"; + return nullptr; + } + + if (items == nullptr) { + LOG(ERROR) << "List of items is null"; + return nullptr; + } + + ItemParams item_params(mime, data_size, item_uri); + item_params.payload_to_serialize = + std::string(reinterpret_cast<const char*>(data), data_size); + items->emplace_back(Item::FromData(item_params)); + + std::unique_ptr<Image> image(std::unique_ptr<Image>(new Image())); // NOLINT + image->item_uri_ = item_uri; + image->item_semantic_ = ImageItemSemantic::kOriginal; + return image; +} + +const string& Image::GetItemUri() const { return item_uri_; } +ImageItemSemantic Image::GetItemSemantic() const { return item_semantic_; } + +std::unique_ptr<Image> Image::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Image()), + DynamicDepthConst::Image()); + if (deserializer == nullptr) { + return nullptr; + } + + std::unique_ptr<Image> image(new Image()); + if (!image->ParseImageFields(*deserializer)) { + return nullptr; + } + return image; +} + +bool Image::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (item_uri_.empty()) { + LOG(ERROR) << "Item URI is empty"; + return false; + } + + return serializer->WriteProperty(DynamicDepthConst::Image(), kItemSemantic, + ItemSemanticToString(item_semantic_)) && + serializer->WriteProperty(DynamicDepthConst::Image(), kItemUri, + item_uri_); +} + +// Private methods. +bool Image::ParseImageFields(const Deserializer& deserializer) { + string item_uri; + string item_semantic_str; + if (!deserializer.ParseString(DynamicDepthConst::Image(), kItemSemantic, + &item_semantic_str) || + !deserializer.ParseString(DynamicDepthConst::Image(), kItemUri, + &item_uri)) { + return false; + } + + item_uri_ = item_uri; + item_semantic_ = StringToItemSemantic(item_semantic_str); + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/imaging_model.cc b/internal/dynamic_depth/imaging_model.cc new file mode 100644 index 0000000..717b8d7 --- /dev/null +++ b/internal/dynamic_depth/imaging_model.cc @@ -0,0 +1,222 @@ +#include "dynamic_depth/imaging_model.h" + +#include <math.h> + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "strings/numbers.h" +#include "xmpmeta/base64.h" + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +constexpr char kFocalLengthX[] = "FocalLengthX"; +constexpr char kFocalLengthY[] = "FocalLengthY"; +constexpr char kImageWidth[] = "ImageWidth"; +constexpr char kImageHeight[] = "ImageHeight"; +constexpr char kPrincipalPointX[] = "PrincipalPointX"; +constexpr char kPrincipalPointY[] = "PrincipalPointY"; +constexpr char kSkew[] = "Skew"; +constexpr char kPixelAspectRatio[] = "PixelAspectRatio"; +constexpr char kDistortion[] = "Distortion"; +constexpr char kDistortionCount[] = "DistortionCount"; + +constexpr char kNamespaceHref[] = + "http://ns.google.com/photos/dd/1.0/imagingmodel/"; + +std::unique_ptr<ImagingModel> ParseFields(const Deserializer& deserializer) { + Point<double> focal_length(0, 0); + Dimension image_size(0, 0); + Point<double> principal_point(0.5, 0.5); + double skew = 0; + double pixel_aspect_ratio = 1.0; + const string& prefix = DynamicDepthConst::ImagingModel(); + + if (!deserializer.ParseDouble(prefix, kFocalLengthX, &focal_length.x) || + !deserializer.ParseDouble(prefix, kFocalLengthY, &focal_length.y) || + !deserializer.ParseInt(prefix, kImageWidth, &image_size.width) || + !deserializer.ParseInt(prefix, kImageHeight, &image_size.height)) { + return nullptr; + } + + double temp1; + double temp2; + if (deserializer.ParseDouble(prefix, kPrincipalPointX, &temp1) && + deserializer.ParseDouble(prefix, kPrincipalPointY, &temp2)) { + principal_point.x = temp1; + principal_point.y = temp2; + } + + if (deserializer.ParseDouble(prefix, kSkew, &temp1)) { + skew = temp1; + } + + if (deserializer.ParseDouble(prefix, kPixelAspectRatio, &temp1)) { + pixel_aspect_ratio = temp1; + } + + int distortion_count = 0; + std::vector<float> distortion; + if (deserializer.ParseInt(DynamicDepthConst::ImagingModel(), kDistortionCount, + &distortion_count)) { + if (distortion_count % 2 != 0) { + LOG(ERROR) << "Parsed DistortionCount = " << distortion_count + << " was expected to be even"; + return nullptr; + } + + deserializer.ParseFloatArrayBase64(DynamicDepthConst::ImagingModel(), + kDistortion, &distortion); + if (distortion.size() != distortion_count * 2) { + LOG(ERROR) << "Parsed DistortionCount of " << distortion_count + << " but should be " << distortion.size() + << " when multiplied by 2"; + return nullptr; + } + } + + ImagingModelParams params(focal_length, image_size); + params.principal_point = principal_point; + params.distortion = distortion; + params.skew = skew; + params.pixel_aspect_ratio = pixel_aspect_ratio; + return ImagingModel::FromData(params); +} + +} // namespace + +// Private constructor. +ImagingModel::ImagingModel(const ImagingModelParams& params) + : params_(params) {} + +// Public methods. +void ImagingModel::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + + ns_name_href_map->emplace(DynamicDepthConst::ImagingModel(), kNamespaceHref); +} + +std::unique_ptr<ImagingModel> ImagingModel::FromData( + const ImagingModelParams& params) { + if (!params.distortion.empty() && params.distortion.size() % 2 != 0) { + LOG(ERROR) << "Distortion must be empty or contain pairs of values, but an " + << " odd number (size=" << params.distortion.size() + << ") was found"; + return nullptr; + } + + return std::unique_ptr<ImagingModel>(new ImagingModel(params)); // NOLINT +} + +std::unique_ptr<ImagingModel> ImagingModel::FromDeserializer( + const xml::Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::ImagingModel()), + DynamicDepthConst::ImagingModel()); + if (deserializer == nullptr) { + LOG(ERROR) << "Deserializer must not be null"; + return nullptr; + } + + return ParseFields(*deserializer); +} + +Point<double> ImagingModel::GetFocalLength() const { + return params_.focal_length; +} + +Point<double> ImagingModel::GetPrincipalPoint() const { + return params_.principal_point; +} + +Dimension ImagingModel::GetImageSize() const { return params_.image_size; } + +double ImagingModel::GetSkew() const { return params_.skew; } + +double ImagingModel::GetPixelAspectRatio() const { + return params_.pixel_aspect_ratio; +} + +const std::vector<float>& ImagingModel::GetDistortion() const { + return params_.distortion; +} + +int ImagingModel::GetDistortionCount() const { + return static_cast<int>(floor(params_.distortion.size() / 2)); +} + +bool ImagingModel::Serialize(xml::Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + // Short-circuiting ensures unnecessary writes will not be performed. + if (!serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kFocalLengthX, + std::to_string(params_.focal_length.x)) || + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kFocalLengthY, + std::to_string(params_.focal_length.y)) || + // Image dimensions. + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), kImageWidth, + std::to_string(params_.image_size.width)) || + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kImageHeight, + std::to_string(params_.image_size.height)) || + // Principal point. + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kPrincipalPointX, + std::to_string(params_.principal_point.x)) || + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kPrincipalPointY, + std::to_string(params_.principal_point.y)) || + // Skew, pixel aspect ratio. + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), kSkew, + std::to_string(params_.skew)) || + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kPixelAspectRatio, + std::to_string(params_.pixel_aspect_ratio))) { + return false; + } + + // Write distortion model only if needed. + if (params_.distortion.empty()) { + return true; + } + + // No error-checking that there are an even number of values in + // params_.distortion, because this is already done in the instantiator and + // deserializer. + string base64_encoded_distortion; + if (!EncodeFloatArrayBase64(params_.distortion, &base64_encoded_distortion)) { + LOG(ERROR) << "Distortion encoding failed"; + return false; + } + + int distortion_count = static_cast<int>(floor(params_.distortion.size() / 2)); + if (!serializer->WriteProperty( + DynamicDepthConst::ImagingModel(), kDistortionCount, + ::dynamic_depth::strings::SimpleItoa(distortion_count))) { + return false; + } + + if (!serializer->WriteProperty(DynamicDepthConst::ImagingModel(), kDistortion, + base64_encoded_distortion)) { + return false; + } + + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/item.cc b/internal/dynamic_depth/item.cc new file mode 100644 index 0000000..a1c2a8f --- /dev/null +++ b/internal/dynamic_depth/item.cc @@ -0,0 +1,139 @@ +#include "dynamic_depth/item.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +constexpr char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/item/"; + +constexpr char kMime[] = "Mime"; +constexpr char kLength[] = "Length"; +constexpr char kPadding[] = "Padding"; +constexpr char kDataUri[] = "DataURI"; + +} // namespace + +// Private constructor. +Item::Item(const ItemParams& params) : params_(params) {} + +// Private instantiator. + +std::unique_ptr<Item> Item::FromDataInternal(const ItemParams& params, + bool check_filepath) { + if (check_filepath && params.length != params.payload_to_serialize.size()) { + LOG(ERROR) << "Length does not match payload's size"; + return nullptr; + } + + if (params.mime.empty()) { + LOG(ERROR) << "Mime is empty"; + return nullptr; + } + + if (params.length < 0) { + LOG(ERROR) << "Item length must be non-negative"; + return nullptr; + } + + if (params.padding > 0 && + static_cast<int>(params.length - params.padding) <= 0) { + LOG(ERROR) << "Item length must be larger than padding; found padding=" + << params.padding << ", length=" << params.length; + return nullptr; + } + + // TODO(miraleung): Check for only supported mime types? + + return std::unique_ptr<Item>(new Item(params)); // NOLINT +} + +void Item::GetNamespaces(std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + + ns_name_href_map->emplace(DynamicDepthConst::Item(), kNamespaceHref); +} + +std::unique_ptr<Item> Item::FromData(const ItemParams& params) { + return FromDataInternal(params, true); +} + +std::unique_ptr<Item> Item::FromDeserializer( + const xml::Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Item()), + DynamicDepthConst::Item()); + if (deserializer == nullptr) { + LOG(ERROR) << "Deserializer must not be null"; + return nullptr; + } + + string mime; + int length; + int padding = 0; + string data_uri; + + if (!deserializer->ParseString(DynamicDepthConst::Item(), kMime, &mime) || + !deserializer->ParseInt(DynamicDepthConst::Item(), kLength, &length)) { + return nullptr; + } + + deserializer->ParseInt(DynamicDepthConst::Item(), kPadding, &padding); + deserializer->ParseString(DynamicDepthConst::Item(), kDataUri, &data_uri); + + ItemParams params(mime, length); + if (!data_uri.empty()) { + params.data_uri = data_uri; + } + if (padding > 0) { + params.padding = padding; + } + + return Item::FromDataInternal(params, false); +} + +// Getters. +const string& Item::GetMime() const { return params_.mime; } +unsigned int Item::GetLength() const { return params_.length; } +const string& Item::GetDataUri() const { return params_.data_uri; } +unsigned int Item::GetPadding() const { return params_.padding; } +const string& Item::GetPayloadToSerialize() const { + return params_.payload_to_serialize; +} + +bool Item::Serialize(xml::Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + // No error-checking for the mime or length here, since it's assumed to be + // taken care of in the instantiator. + bool success = serializer->WriteProperty(DynamicDepthConst::Item(), kMime, + params_.mime) && + serializer->WriteProperty(DynamicDepthConst::Item(), kLength, + std::to_string(params_.length)); + if (!params_.data_uri.empty()) { + success &= serializer->WriteProperty(DynamicDepthConst::Item(), kDataUri, + params_.data_uri); + } + + if (params_.padding > 0) { + success &= serializer->WriteProperty(DynamicDepthConst::Item(), kPadding, + std::to_string(params_.padding)); + } + + return success; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/light_estimate.cc b/internal/dynamic_depth/light_estimate.cc new file mode 100644 index 0000000..9ce3bab --- /dev/null +++ b/internal/dynamic_depth/light_estimate.cc @@ -0,0 +1,128 @@ +#include "dynamic_depth/light_estimate.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "xmpmeta/base64.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { +constexpr int kColorCorrectionSize = 3; + +constexpr char kPixelIntensity[] = "PixelIntensity"; +constexpr char kColorCorrectionR[] = "ColorCorrectionR"; +constexpr char kColorCorrectionG[] = "ColorCorrectionG"; +constexpr char kColorCorrectionB[] = "ColorCorrectionB"; + +constexpr char kNamespaceHref[] = + "http://ns.google.com/photos/dd/1.0/lightestimate/"; + +} // namespace + +// Private constructor. +LightEstimate::LightEstimate() {} + +// Public methods. +void LightEstimate::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::LightEstimate(), kNamespaceHref); +} + +std::unique_ptr<LightEstimate> LightEstimate::FromData(float pixel_intensity) { + return LightEstimate::FromData(pixel_intensity, {1.0f, 1.0f, 1.0f}); +} + +std::unique_ptr<LightEstimate> LightEstimate::FromData( + float pixel_intensity, const std::vector<float>& color_correction) { + // Priate constructor. + std::unique_ptr<LightEstimate> light_estimate( + std::unique_ptr<LightEstimate>(new LightEstimate())); // NOLINT + light_estimate->pixel_intensity_ = pixel_intensity; + + if (color_correction.size() >= kColorCorrectionSize) { + std::copy(color_correction.begin(), + color_correction.begin() + kColorCorrectionSize, + light_estimate->color_correction_.begin()); + } else { + LOG(WARNING) << "Color correction had fewer than three values, " + << "reverting to default of 1.0 for all RGB values"; + } + + return light_estimate; +} + +float LightEstimate::GetPixelIntensity() const { return pixel_intensity_; } + +const std::vector<float>& LightEstimate::GetColorCorrection() const { + return color_correction_; +} + +std::unique_ptr<LightEstimate> LightEstimate::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::LightEstimate()), + DynamicDepthConst::LightEstimate()); + if (deserializer == nullptr) { + return nullptr; + } + + std::unique_ptr<LightEstimate> light_estimate( + std::unique_ptr<LightEstimate>(new LightEstimate())); // NOLINT + if (!deserializer->ParseFloat(DynamicDepthConst::LightEstimate(), + kPixelIntensity, + &light_estimate->pixel_intensity_)) { + return nullptr; + } + + float color_correction_r; + float color_correction_g; + float color_correction_b; + if (deserializer->ParseFloat(DynamicDepthConst::LightEstimate(), + kColorCorrectionR, &color_correction_r) && + deserializer->ParseFloat(DynamicDepthConst::LightEstimate(), + kColorCorrectionG, &color_correction_g) && + deserializer->ParseFloat(DynamicDepthConst::LightEstimate(), + kColorCorrectionB, &color_correction_b)) { + light_estimate->color_correction_[0] = color_correction_r; + light_estimate->color_correction_[1] = color_correction_g; + light_estimate->color_correction_[2] = color_correction_b; + } + + return light_estimate; +} + +bool LightEstimate::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (!serializer->WriteProperty(DynamicDepthConst::LightEstimate(), + kPixelIntensity, + std::to_string(pixel_intensity_))) { + return false; + } + + CHECK(color_correction_.size() >= 3) + << "Color correction not initialized to a size-3 vector"; + return serializer->WriteProperty(DynamicDepthConst::LightEstimate(), + kColorCorrectionR, + std::to_string(color_correction_[0])) && + serializer->WriteProperty(DynamicDepthConst::LightEstimate(), + kColorCorrectionG, + std::to_string(color_correction_[1])) && + serializer->WriteProperty(DynamicDepthConst::LightEstimate(), + kColorCorrectionB, + std::to_string(color_correction_[2])); +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/plane.cc b/internal/dynamic_depth/plane.cc new file mode 100644 index 0000000..7e3914e --- /dev/null +++ b/internal/dynamic_depth/plane.cc @@ -0,0 +1,177 @@ +#include "dynamic_depth/plane.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "strings/numbers.h" +#include "xmpmeta/base64.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +constexpr char kBoundary[] = "Boundary"; +constexpr char kBoundaryVertexCount[] = "BoundaryVertexCount"; +constexpr char kExtentX[] = "ExtentX"; +constexpr char kExtentZ[] = "ExtentZ"; + +constexpr char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/plane/"; + +} // namespace + +// Private constructor. +Plane::Plane() {} + +// Public methods. +void Plane::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::Plane(), kNamespaceHref); + + if (pose_ != nullptr) { + pose_->GetNamespaces(ns_name_href_map); + } +} + +std::unique_ptr<Plane> Plane::FromData(std::unique_ptr<Pose> pose, + const std::vector<float>& boundary, + const double extent_x, + const double extent_z) { + if (pose == nullptr) { + LOG(ERROR) << "The Plane's pose must be provided"; + return nullptr; + } + + if (boundary.size() % 2 != 0) { + LOG(ERROR) << "Number of vertices in the boundary polygon must be 2-tuples"; + return nullptr; + } + + std::unique_ptr<Plane> plane(std::unique_ptr<Plane>(new Plane())); // NOLINT + plane->pose_ = std::move(pose); + plane->boundary_vertex_count_ = boundary.size() / 2; + if (!boundary.empty()) { + plane->boundary_ = boundary; + } + + plane->extent_x_ = extent_x; + plane->extent_z_ = extent_z; + return plane; +} + +std::unique_ptr<Plane> Plane::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Plane()), + DynamicDepthConst::Plane()); + if (deserializer == nullptr) { + LOG(ERROR) << "Deserializer must not be null"; + return nullptr; + } + + std::unique_ptr<Plane> plane(std::unique_ptr<Plane>(new Plane())); // NOLINT + if (!plane->ParsePlaneFields(*deserializer)) { + return nullptr; + } + + return plane; +} + +const Pose* Plane::GetPose() const { return pose_.get(); } + +const std::vector<float>& Plane::GetBoundary() const { return boundary_; } + +int Plane::GetBoundaryVertexCount() const { return boundary_vertex_count_; } + +const double Plane::GetExtentX() const { return extent_x_; } + +const double Plane::GetExtentZ() const { return extent_z_; } + +bool Plane::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (pose_ == nullptr) { + LOG(ERROR) << "Plane's pose must be present, not serializing"; + return false; + } + + if (!serializer->WriteProperty( + DynamicDepthConst::Plane(), kBoundaryVertexCount, + ::dynamic_depth::strings::SimpleItoa(boundary_vertex_count_))) { + return false; + } + if (!boundary_.empty()) { + string base64_encoded_boundary; + if (!EncodeFloatArrayBase64(boundary_, &base64_encoded_boundary)) { + LOG(ERROR) << "Boundary polygon encoding failed."; + return false; + } + + if (!serializer->WriteProperty(DynamicDepthConst::Plane(), kBoundary, + base64_encoded_boundary)) { + return false; + } + } + + if (!serializer->WriteProperty(DynamicDepthConst::Plane(), kExtentX, + std::to_string(extent_x_))) { + return false; + } + + if (!serializer->WriteProperty(DynamicDepthConst::Plane(), kExtentZ, + std::to_string(extent_z_))) { + return false; + } + + std::unique_ptr<Serializer> pose_serializer = serializer->CreateSerializer( + DynamicDepthConst::Plane(), DynamicDepthConst::Pose()); + return pose_->Serialize(pose_serializer.get()); +} + +// Private methods. +bool Plane::ParsePlaneFields(const Deserializer& deserializer) { + std::unique_ptr<Pose> pose = + Pose::FromDeserializer(deserializer, DynamicDepthConst::Plane()); + if (pose == nullptr) { + LOG(ERROR) << "Plane's pose could not be parsed, stopping deserialization"; + return false; + } + + // The BoundaryVertexCount field is required only if the Boundary field is + // populated. + std::vector<float> boundary; + int boundary_vertex_count = 0; + if (deserializer.ParseFloatArrayBase64(DynamicDepthConst::Plane(), kBoundary, + &boundary)) { + if (!deserializer.ParseInt(DynamicDepthConst::Plane(), kBoundaryVertexCount, + &boundary_vertex_count)) { + return false; + } + } + + double extent_x = -1; + deserializer.ParseDouble(DynamicDepthConst::Plane(), kExtentX, &extent_x); + + double extent_z = -1; + deserializer.ParseDouble(DynamicDepthConst::Plane(), kExtentZ, &extent_z); + + pose_ = std::move(pose); + boundary_ = boundary; + boundary_vertex_count_ = boundary_vertex_count; + extent_x_ = extent_x; + extent_z_ = extent_z; + + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/planes.cc b/internal/dynamic_depth/planes.cc new file mode 100644 index 0000000..5eb767f --- /dev/null +++ b/internal/dynamic_depth/planes.cc @@ -0,0 +1,116 @@ +#include "dynamic_depth/planes.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +namespace photos_editing_formats { +namespace dynamic_depth { + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +// Private constructor. +Planes::Planes() = default; + +void Planes::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr || plane_list_.empty()) { + LOG(ERROR) << "Namespace list is null or plane list is empty"; + return; + } + for (const auto& plane : plane_list_) { + plane->GetNamespaces(ns_name_href_map); + } +} + +std::unique_ptr<Planes> Planes::FromPlaneArray( + std::vector<std::unique_ptr<Plane>>* plane_list) { + if (plane_list == nullptr || plane_list->empty()) { + LOG(ERROR) << "Plane list is empty"; + return nullptr; + } + + for (const auto& plane : *plane_list) { + if (!plane) { + LOG(ERROR) << "plane_list cannot contain null elements"; + return nullptr; + } + } + + // Using `new` to access a non-public constructor. + std::unique_ptr<Planes> planes(new Planes()); // NOLINT + std::swap(*plane_list, planes->plane_list_); + return planes; +} + +std::unique_ptr<Planes> Planes::FromDeserializer( + const Deserializer& parent_deserializer) { + // Using `new` to access a non-public constructor. + std::unique_ptr<Planes> planes(new Planes()); // NOLINT + int i = 0; + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(DynamicDepthConst::Planes()), + DynamicDepthConst::Planes(), 0); + while (deserializer) { + std::unique_ptr<Plane> plane = Plane::FromDeserializer(*deserializer); + if (plane == nullptr) { + LOG(ERROR) << "Unable to deserialize a plane"; + return nullptr; + } + + planes->plane_list_.push_back(std::move(plane)); + i++; + deserializer = parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(DynamicDepthConst::Planes()), + DynamicDepthConst::Planes(), i); + } + + if (planes->plane_list_.empty()) { + return nullptr; + } + return planes; +} + +int Planes::GetPlaneCount() const { return plane_list_.size(); } + +const Plane* Planes::GetPlaneAt(int i) const { return plane_list_[i].get(); } + +bool Planes::Serialize(Serializer* serializer) const { + if (plane_list_.empty()) { + LOG(ERROR) << "Plane list is empty"; + return false; + } + std::unique_ptr<Serializer> planes_serializer = + serializer->CreateListSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Planes()), + DynamicDepthConst::Planes()); + if (planes_serializer == nullptr) { + // Error is logged in Serializer. + return false; + } + for (int i = 0; i < plane_list_.size(); i++) { + std::unique_ptr<Serializer> plane_serializer = + planes_serializer->CreateItemSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Plane()), + DynamicDepthConst::Plane()); + if (plane_serializer == nullptr) { + LOG(ERROR) << "Could not create a list item serializer for Plane"; + return false; + } + + if (plane_list_[i] == nullptr) { + LOG(ERROR) << "Plane " << i << " is null, could not serialize"; + continue; + } + + if (!plane_list_[i]->Serialize(plane_serializer.get())) { + LOG(ERROR) << "Could not serialize plane " << i; + continue; + } + } + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/point.h b/internal/dynamic_depth/point.h new file mode 100644 index 0000000..c6eecbb --- /dev/null +++ b/internal/dynamic_depth/point.h @@ -0,0 +1,25 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_POINT_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_POINT_H_ // NOLINT + +namespace photos_editing_formats { +namespace dynamic_depth { + +// A struct that contains the width and height of a size or the x and y +// coordinates of a point. +template <typename T> +struct Point { + Point(T x_coord, T y_coord) : x(x_coord), y(y_coord) {} + T x; + T y; + + inline bool operator==(const Point& other) const { + return x == other.x && y == other.y; + } + + inline bool operator!=(const Point& other) const { return !(*this == other); } +}; + +} // namespace dynamic_depth +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_POINT_H_ // NOLINT diff --git a/internal/dynamic_depth/point_cloud.cc b/internal/dynamic_depth/point_cloud.cc new file mode 100644 index 0000000..f35e9c1 --- /dev/null +++ b/internal/dynamic_depth/point_cloud.cc @@ -0,0 +1,164 @@ +#include "dynamic_depth/point_cloud.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "strings/numbers.h" +#include "xmpmeta/base64.h" +#include "xmpmeta/xml/utils.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kPropertyPrefix[] = "PointCloud"; +const char kPointCount[] = "PointCount"; +const char kPoints[] = "Points"; +const char kMetric[] = "Metric"; + +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/pointcloud/"; + +} // namespace + +// Private constructor. +PointCloud::PointCloud() : metric_(false) {} + +// Public methods. +void PointCloud::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->insert( + std::pair<string, string>(kPropertyPrefix, kNamespaceHref)); +} + +std::unique_ptr<PointCloud> PointCloud::FromData( + const std::vector<float>& points, bool metric) { + if (points.empty()) { + LOG(ERROR) << "No point data given"; + return nullptr; + } + + if (points.size() % 3 != 0) { + LOG(ERROR) << "Points must be (x, y, z) tuples, so the size must be " + << "divisible by 3, got " << points.size(); + return nullptr; + } + + std::unique_ptr<PointCloud> point_cloud(new PointCloud()); + point_cloud->points_ = points; + point_cloud->metric_ = metric; + return point_cloud; +} + +std::unique_ptr<PointCloud> PointCloud::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(kPropertyPrefix), kPropertyPrefix); + if (deserializer == nullptr) { + return nullptr; + } + + std::unique_ptr<PointCloud> point_cloud(new PointCloud()); + if (!point_cloud->ParseFields(*deserializer)) { + return nullptr; + } + return point_cloud; +} + +int PointCloud::GetPointCount() const { + return static_cast<int>(points_.size() / 3); +} + +const std::vector<float>& PointCloud::GetPoints() const { return points_; } +bool PointCloud::GetMetric() const { return metric_; } + +bool PointCloud::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (points_.empty()) { + LOG(ERROR) << "No points in the PointCloud to serialize"; + return false; + } + + // No error checking (e.g. points_.size() % 3 == 0), because serialization + // shouldn't be blocked by this. + string base64_encoded_points; + if (!EncodeFloatArrayBase64(points_, &base64_encoded_points)) { + LOG(ERROR) << "Points encoding failed"; + return false; + } + + // Write required fields. + int point_count = static_cast<int>(points_.size() / 3); + if (!serializer->WriteProperty( + DynamicDepthConst::PointCloud(), kPointCount, + ::dynamic_depth::strings::SimpleItoa(point_count))) { + return false; + } + + if (!serializer->WriteProperty(DynamicDepthConst::PointCloud(), kPoints, + base64_encoded_points)) { + return false; + } + + // Write optional fields. + serializer->WriteBoolProperty(DynamicDepthConst::PointCloud(), kMetric, + metric_); + return true; +} + +// Private methods. +bool PointCloud::ParseFields(const Deserializer& deserializer) { + // Required fields. + std::vector<float> points; + if (!deserializer.ParseFloatArrayBase64(DynamicDepthConst::PointCloud(), + kPoints, &points)) { + return false; + } + + int point_count; + if (!deserializer.ParseInt(DynamicDepthConst::PointCloud(), kPointCount, + &point_count)) { + return false; + } + + if (points.size() % 3 != 0) { + LOG(ERROR) << "Parsed " << points.size() << " values but expected the size " + << "to be divisible by 3 for (x, y, z) tuple representation"; + return false; + } + + int parsed_points_count = static_cast<int>(points.size() / 3); + if (parsed_points_count != point_count) { + LOG(ERROR) << "Parsed PointCount = " << point_count << " but " + << parsed_points_count << " points were found"; + return false; + } + + // We know that point_count == points.size() now. + points_ = points; + + // Optional fields. + bool metric; + if (!deserializer.ParseBoolean(DynamicDepthConst::PointCloud(), kMetric, + &metric)) { + // Set it to the default value. + metric_ = false; + } else { + metric_ = metric; + } + + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/pose.cc b/internal/dynamic_depth/pose.cc new file mode 100644 index 0000000..7e02588 --- /dev/null +++ b/internal/dynamic_depth/pose.cc @@ -0,0 +1,176 @@ +#include "dynamic_depth/pose.h" + +#include <math.h> + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kPositionX[] = "PositionX"; +const char kPositionY[] = "PositionY"; +const char kPositionZ[] = "PositionZ"; +const char kRotationX[] = "RotationX"; +const char kRotationY[] = "RotationY"; +const char kRotationZ[] = "RotationZ"; +const char kRotationW[] = "RotationW"; +const char kTimestamp[] = "Timestamp"; +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/pose/"; + +const std::vector<float> NormalizeQuaternion(const std::vector<float>& quat) { + if (quat.size() < 4) { + return std::vector<float>(); + } + float length = sqrt((quat[0] * quat[0]) + (quat[1] * quat[1]) + + (quat[2] * quat[2]) + (quat[3] * quat[3])); + const std::vector<float> normalized = {quat[0] / length, quat[1] / length, + quat[2] / length, quat[3] / length}; + return normalized; +} + +} // namespace + +// Private constructor. +Pose::Pose() : timestamp_(-1) {} + +// Public methods. +void Pose::GetNamespaces(std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::Pose(), kNamespaceHref); +} + +std::unique_ptr<Pose> Pose::FromData(const std::vector<float>& position, + const std::vector<float>& orientation, + const int64 timestamp) { + if (position.empty() && orientation.empty()) { + LOG(ERROR) << "Either position or orientation must be provided"; + return nullptr; + } + + std::unique_ptr<Pose> pose(new Pose()); + if (position.size() >= 3) { + pose->position_ = position; + } + + if (orientation.size() >= 4) { + pose->orientation_ = NormalizeQuaternion(orientation); + } + + if (timestamp >= 0) { + pose->timestamp_ = timestamp; + } + + return pose; +} + +std::unique_ptr<Pose> Pose::FromDeserializer( + const Deserializer& parent_deserializer, const char* parent_namespace) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer(parent_namespace, + DynamicDepthConst::Pose()); + if (deserializer == nullptr) { + return nullptr; + } + std::unique_ptr<Pose> pose(new Pose()); + if (!pose->ParsePoseFields(*deserializer)) { + return nullptr; + } + return pose; +} + +bool Pose::HasPosition() const { return position_.size() == 3; } +bool Pose::HasOrientation() const { return orientation_.size() == 4; } + +const std::vector<float>& Pose::GetPosition() const { return position_; } + +const std::vector<float>& Pose::GetOrientation() const { return orientation_; } + +int64 Pose::GetTimestamp() const { return timestamp_; } + +bool Pose::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (!HasPosition() && !HasOrientation()) { + LOG(ERROR) << "Camera pose has neither position nor orientation"; + return false; + } + + bool success = true; + if (position_.size() == 3) { + success &= serializer->WriteProperty(DynamicDepthConst::Pose(), kPositionX, + std::to_string(position_[0])) && + serializer->WriteProperty(DynamicDepthConst::Pose(), kPositionY, + std::to_string(position_[1])) && + serializer->WriteProperty(DynamicDepthConst::Pose(), kPositionZ, + std::to_string(position_[2])); + } + + if (orientation_.size() == 4) { + success &= serializer->WriteProperty(DynamicDepthConst::Pose(), kRotationX, + std::to_string(orientation_[0])) && + serializer->WriteProperty(DynamicDepthConst::Pose(), kRotationY, + std::to_string(orientation_[1])) && + serializer->WriteProperty(DynamicDepthConst::Pose(), kRotationZ, + std::to_string(orientation_[2])) && + serializer->WriteProperty(DynamicDepthConst::Pose(), kRotationW, + std::to_string(orientation_[3])); + } + + if (timestamp_ >= 0) { + serializer->WriteProperty(DynamicDepthConst::Pose(), kTimestamp, + std::to_string(timestamp_)); + } + + return success; +} + +// Private methods. +bool Pose::ParsePoseFields(const Deserializer& deserializer) { + float x, y, z, w; + const string& prefix = DynamicDepthConst::Pose(); + // If a position field is present, the rest must be as well. + if (deserializer.ParseFloat(prefix, kPositionX, &x)) { + if (!deserializer.ParseFloat(prefix, kPositionY, &y)) { + return false; + } + if (!deserializer.ParseFloat(prefix, kPositionZ, &z)) { + return false; + } + position_ = {x, y, z}; + } + + // Same for orientation. + if (deserializer.ParseFloat(prefix, kRotationX, &x)) { + if (!deserializer.ParseFloat(prefix, kRotationY, &y)) { + return false; + } + if (!deserializer.ParseFloat(prefix, kRotationZ, &z)) { + return false; + } + if (!deserializer.ParseFloat(prefix, kRotationW, &w)) { + return false; + } + orientation_ = std::vector<float>({x, y, z, w}); + } + + if (position_.size() < 3 && orientation_.size() < 4) { + return false; + } + + deserializer.ParseLong(prefix, kTimestamp, ×tamp_); + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/profile.cc b/internal/dynamic_depth/profile.cc new file mode 100644 index 0000000..079f9c2 --- /dev/null +++ b/internal/dynamic_depth/profile.cc @@ -0,0 +1,138 @@ +#include "dynamic_depth/profile.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kType[] = "Type"; +const char kCameraIndices[] = "CameraIndices"; + +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/profile/"; + +// Profile type names. +constexpr char kArPhoto[] = "ARPhoto"; +constexpr char kArPhotoLowercase[] = "arphoto"; +constexpr char kDepthPhoto[] = "DepthPhoto"; +constexpr char kDepthPhotoLowercase[] = "depthphoto"; + +// Profile camera indices' sizes. +constexpr size_t kArPhotoIndicesSize = 1; +constexpr size_t kDepthPhotoIndicesSize = 1; + +// Returns true if the type is unsupported, or if the type is supported in the +// Dynamic Depth Profile element and the size of the camera indices matches that +// specified in the spec. +bool ValidateKnownTypeAndIndices(const string& type, size_t camera_indices_size, + string* matched_type) { + string type_lower = type; + std::transform(type_lower.begin(), type_lower.end(), type_lower.begin(), + ::tolower); + bool isArPhoto = (kArPhotoLowercase == type_lower); + bool isDepthPhoto = (kDepthPhotoLowercase == type_lower); + if (!isArPhoto && !isDepthPhoto) { + return true; + } + bool matches = + (isArPhoto && camera_indices_size >= kArPhotoIndicesSize) || + (isDepthPhoto && camera_indices_size >= kDepthPhotoIndicesSize); + if (!matches) { + LOG(WARNING) << "Size of camera indices for " + << (isArPhoto ? kArPhoto : kDepthPhoto) << " must be at least " + << (isArPhoto ? kArPhotoIndicesSize : kDepthPhotoIndicesSize); + } else { + *matched_type = isArPhoto ? kArPhoto : kDepthPhoto; + } + + return matches; +} + +} // namespace + +// Private constructor. +Profile::Profile(const string& type, const std::vector<int>& camera_indices) + : type_(type), camera_indices_(camera_indices) {} + +// Public methods. +void Profile::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::Profile(), kNamespaceHref); +} + +std::unique_ptr<Profile> Profile::FromData( + const string& type, const std::vector<int>& camera_indices) { + if (type.empty()) { + LOG(ERROR) << "Profile must have a type"; + return nullptr; + } + // Check that the camera indices' length is at least the size of that + // specified for the type. This has no restrictions on unsupported profile + // types. + // Ensure also that a consistent case-sensitive profile is stored, even if the + // caller provided a case-insensitive name. + string matched_type; + if (!ValidateKnownTypeAndIndices(type, camera_indices.size(), + &matched_type)) { + return nullptr; + } + + return std::unique_ptr<Profile>( + new Profile(matched_type.empty() ? type : + matched_type, camera_indices)); // NOLINT +} + +std::unique_ptr<Profile> Profile::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Profile()), + DynamicDepthConst::Profile()); + if (deserializer == nullptr) { + return nullptr; + } + std::unique_ptr<Profile> profile(new Profile("", {})); + if (!deserializer->ParseString(DynamicDepthConst::Profile(), kType, + &profile->type_)) { + return nullptr; + } + deserializer->ParseIntArray(DynamicDepthConst::Profile(), kCameraIndices, + &profile->camera_indices_); + if (!ValidateKnownTypeAndIndices( + profile->type_, profile->camera_indices_.size(), &profile->type_)) { + return nullptr; + } + return profile; +} + +const string& Profile::GetType() const { return type_; } + +const std::vector<int>& Profile::GetCameraIndices() const { + return camera_indices_; +} + +bool Profile::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + if (!serializer->WriteProperty(DynamicDepthConst::Profile(), kType, type_)) { + return false; + } + if (camera_indices_.empty()) { + return true; + } + return serializer->WriteIntArray(DynamicDepthConst::Profile(), kCameraIndices, + camera_indices_); +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/profiles.cc b/internal/dynamic_depth/profiles.cc new file mode 100644 index 0000000..b23b5d7 --- /dev/null +++ b/internal/dynamic_depth/profiles.cc @@ -0,0 +1,99 @@ +#include "dynamic_depth/profiles.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { + +void Profiles::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr || profile_list_.empty()) { + LOG(ERROR) << "Namespace list is null or profile list is empty"; + return; + } + for (const auto& profile : profile_list_) { + profile->GetNamespaces(ns_name_href_map); + } +} + +std::unique_ptr<Profiles> Profiles::FromProfileArray( + std::vector<std::unique_ptr<Profile>>* profile_list) { + if (profile_list->empty()) { + LOG(ERROR) << "Profile list is empty"; + return nullptr; + } + std::unique_ptr<Profiles> profiles(new Profiles()); + profiles->profile_list_ = std::move(*profile_list); + return profiles; +} + +std::unique_ptr<Profiles> Profiles::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Profiles> profiles(new Profiles()); + int i = 0; + for (std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(DynamicDepthConst::Profiles()), + DynamicDepthConst::Profiles(), i); + deserializer != nullptr; + deserializer = parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(DynamicDepthConst::Profiles()), + DynamicDepthConst::Profiles(), ++i)) { + std::unique_ptr<Profile> profile = Profile::FromDeserializer(*deserializer); + if (profile != nullptr) { + profiles->profile_list_.emplace_back(std::move(profile)); + } + } + + if (profiles->profile_list_.empty()) { + return nullptr; + } + return profiles; +} + +const std::vector<const Profile*> Profiles::GetProfiles() const { + std::vector<const Profile*> profile_list; + for (const auto& profile : profile_list_) { + profile_list.push_back(profile.get()); + } + return profile_list; +} + +bool Profiles::Serialize(Serializer* serializer) const { + if (profile_list_.empty()) { + LOG(ERROR) << "Profile list is empty"; + return false; + } + bool success = true; + int i = 0; + std::unique_ptr<Serializer> profiles_serializer = + serializer->CreateListSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Profiles()), + DynamicDepthConst::Profiles()); + if (profiles_serializer == nullptr) { + // Error is logged in Serializer. + return false; + } + for (const auto& profile : profile_list_) { + std::unique_ptr<Serializer> profile_serializer = + profiles_serializer->CreateItemSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Profile()), + DynamicDepthConst::Profile()); + if (profile_serializer == nullptr) { + continue; + } + success &= profile->Serialize(profile_serializer.get()); + if (!success) { + LOG(ERROR) << "Could not serialize profile " << i; + } + ++i; + } + return success; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/vendor_info.cc b/internal/dynamic_depth/vendor_info.cc new file mode 100644 index 0000000..db73840 --- /dev/null +++ b/internal/dynamic_depth/vendor_info.cc @@ -0,0 +1,108 @@ +#include "dynamic_depth/vendor_info.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "xmpmeta/base64.h" +#include "xmpmeta/xml/utils.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kPropertyPrefix[] = "VendorInfo"; +const char kModel[] = "Model"; +const char kManufacturer[] = "Manufacturer"; +const char kNotes[] = "Notes"; + +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/vendorinfo/"; + +} // namespace + +// Private constructor. +VendorInfo::VendorInfo() : manufacturer_(""), model_(""), notes_("") {} + +// Public methods. +void VendorInfo::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->insert( + std::pair<string, string>(kPropertyPrefix, kNamespaceHref)); +} + +std::unique_ptr<VendorInfo> VendorInfo::FromData(const string& manufacturer, + const string& model, + const string& notes) { + if (manufacturer.empty()) { + LOG(ERROR) << "No manufacturer data given"; + return nullptr; + } + std::unique_ptr<VendorInfo> vendor_info(new VendorInfo()); + vendor_info->model_ = model; + vendor_info->manufacturer_ = manufacturer; + vendor_info->notes_ = notes; + return vendor_info; +} + +std::unique_ptr<VendorInfo> VendorInfo::FromDeserializer( + const Deserializer& parent_deserializer, const string& namespace_str) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer(namespace_str, kPropertyPrefix); + if (deserializer == nullptr) { + return nullptr; + } + + std::unique_ptr<VendorInfo> vendor_info(new VendorInfo()); + if (!vendor_info->ParseFields(*deserializer)) { + return nullptr; + } + return vendor_info; +} + +const string& VendorInfo::GetManufacturer() const { return manufacturer_; } +const string& VendorInfo::GetModel() const { return model_; } +const string& VendorInfo::GetNotes() const { return notes_; } + +bool VendorInfo::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + // Write required field. + if (!serializer->WriteProperty(DynamicDepthConst::VendorInfo(), kManufacturer, + manufacturer_)) { + return false; + } + + // Write optional fields. + if (!model_.empty()) { + serializer->WriteProperty(DynamicDepthConst::VendorInfo(), kModel, model_); + } + if (!notes_.empty()) { + serializer->WriteProperty(DynamicDepthConst::VendorInfo(), kNotes, notes_); + } + return true; +} + +// Private methods. +bool VendorInfo::ParseFields(const Deserializer& deserializer) { + // Required field. + if (!deserializer.ParseString(DynamicDepthConst::VendorInfo(), kManufacturer, + &manufacturer_)) { + return false; + } + + // Optional fields. + deserializer.ParseString(DynamicDepthConst::VendorInfo(), kModel, &model_); + deserializer.ParseString(DynamicDepthConst::VendorInfo(), kNotes, ¬es_); + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/strings/ascii_ctype.cc b/internal/strings/ascii_ctype.cc new file mode 100644 index 0000000..0db868d --- /dev/null +++ b/internal/strings/ascii_ctype.cc @@ -0,0 +1,114 @@ +#include "strings/ascii_ctype.h" + +namespace dynamic_depth { + +// # Table generated by this Python code (bit 0x02 is currently unused): +// def Hex2(n): +// return '0x' + hex(n/16)[2:] + hex(n%16)[2:] +// def IsPunct(ch): +// return (ord(ch) >= 32 and ord(ch) < 127 and +// not ch.isspace() and not ch.isalnum()) +// def IsBlank(ch): +// return ch in ' \t' +// def IsCntrl(ch): +// return ord(ch) < 32 or ord(ch) == 127 +// def IsXDigit(ch): +// return ch.isdigit() or ch.lower() in 'abcdef' +// for i in range(128): +// ch = chr(i) +// mask = ((ch.isalpha() and 0x01 or 0) | +// (ch.isalnum() and 0x04 or 0) | +// (ch.isspace() and 0x08 or 0) | +// (IsPunct(ch) and 0x10 or 0) | +// (IsBlank(ch) and 0x20 or 0) | +// (IsCntrl(ch) and 0x40 or 0) | +// (IsXDigit(ch) and 0x80 or 0)) +// print Hex2(mask) + ',', +// if i % 16 == 7: +// print ' //', Hex2(i & 0x78) +// elif i % 16 == 15: +// print +const unsigned char kAsciiPropertyBits[256] = { + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x00 + 0x40, 0x68, 0x48, 0x48, 0x48, 0x48, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x10 + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // 0x20 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, // 0x30 + 0x84, 0x84, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05, // 0x40 + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, // 0x50 + 0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05, // 0x60 + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, // 0x70 + 0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x40, +}; + +const char kAsciiToLower[256] = { + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', + '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', + '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', + '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '\x20', '\x21', '\x22', '\x23', + '\x24', '\x25', '\x26', '\x27', '\x28', '\x29', '\x2a', '\x2b', '\x2c', + '\x2d', '\x2e', '\x2f', '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', + '\x36', '\x37', '\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', + '\x3f', '\x40', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f', '\x60', '\x61', '\x62', + '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', '\x69', '\x6a', '\x6b', + '\x6c', '\x6d', '\x6e', '\x6f', '\x70', '\x71', '\x72', '\x73', '\x74', + '\x75', '\x76', '\x77', '\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', + '\x7e', '\x7f', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', + '\x87', '\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', + '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', + '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f', '\xa0', '\xa1', + '\xa2', '\xa3', '\xa4', '\xa5', '\xa6', '\xa7', '\xa8', '\xa9', '\xaa', + '\xab', '\xac', '\xad', '\xae', '\xaf', '\xb0', '\xb1', '\xb2', '\xb3', + '\xb4', '\xb5', '\xb6', '\xb7', '\xb8', '\xb9', '\xba', '\xbb', '\xbc', + '\xbd', '\xbe', '\xbf', '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', + '\xc6', '\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', + '\xcf', '\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd7', + '\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf', '\xe0', + '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', + '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef', '\xf0', '\xf1', '\xf2', + '\xf3', '\xf4', '\xf5', '\xf6', '\xf7', '\xf8', '\xf9', '\xfa', '\xfb', + '\xfc', '\xfd', '\xfe', '\xff', +}; + +const char kAsciiToUpper[256] = { + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', + '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', + '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', + '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '\x20', '\x21', '\x22', '\x23', + '\x24', '\x25', '\x26', '\x27', '\x28', '\x29', '\x2a', '\x2b', '\x2c', + '\x2d', '\x2e', '\x2f', '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', + '\x36', '\x37', '\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', + '\x3f', '\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', + '\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', '\x50', + '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57', '\x58', '\x59', + '\x5a', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f', '\x60', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', + 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', '\x7b', '\x7c', '\x7d', + '\x7e', '\x7f', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', + '\x87', '\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', + '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', + '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f', '\xa0', '\xa1', + '\xa2', '\xa3', '\xa4', '\xa5', '\xa6', '\xa7', '\xa8', '\xa9', '\xaa', + '\xab', '\xac', '\xad', '\xae', '\xaf', '\xb0', '\xb1', '\xb2', '\xb3', + '\xb4', '\xb5', '\xb6', '\xb7', '\xb8', '\xb9', '\xba', '\xbb', '\xbc', + '\xbd', '\xbe', '\xbf', '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', + '\xc6', '\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', + '\xcf', '\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd7', + '\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf', '\xe0', + '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', + '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef', '\xf0', '\xf1', '\xf2', + '\xf3', '\xf4', '\xf5', '\xf6', '\xf7', '\xf8', '\xf9', '\xfa', '\xfb', + '\xfc', '\xfd', '\xfe', '\xff', +}; + +} // namespace dynamic_depth diff --git a/internal/strings/ascii_ctype.h b/internal/strings/ascii_ctype.h new file mode 100644 index 0000000..acc1fe5 --- /dev/null +++ b/internal/strings/ascii_ctype.h @@ -0,0 +1,89 @@ +// Character classification functions similar to standard <ctype.h>. +// Some C++ implementations provide locale-sensitive implementations +// of some <ctype.h> functions. These ascii_* functions are +// hard-wired for ASCII. Hard-wired for ASCII is much faster. +// +// ascii_isalnum, ascii_isalpha, ascii_isascii, ascii_isblank, +// ascii_iscntrl, ascii_isdigit, ascii_isgraph, ascii_islower, +// ascii_isprint, ascii_ispunct, ascii_isspace, ascii_isupper, +// ascii_isxdigit +// Similar to the <ctype.h> functions with similar names. +// Input parameter is an unsigned char. Return value is a bool. +// If the input has a numerical value greater than 127 +// then the output is "false". +// +// ascii_tolower, ascii_toupper +// Similar to the <ctype.h> functions with similar names. +// Input parameter is an unsigned char. Return value is a char. +// If the input is not an ascii {lower,upper}-case letter +// (including numerical values greater than 127) +// then the output is the same as the input. + +#ifndef DYNAMIC_DEPTH_INTERNAL_STRINGS_ASCII_CTYPE_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_STRINGS_ASCII_CTYPE_H_ // NOLINT + +namespace dynamic_depth { + +// Array of character information. This is an implementation detail. +// The individual bits do not have names because the array definition is +// already tightly coupled to these functions. Names would just make it +// harder to read and debug. + +extern const unsigned char kAsciiPropertyBits[256]; + +// Public functions. + +static inline bool ascii_isalpha(unsigned char c) { + return (kAsciiPropertyBits[c] & 0x01) != 0; +} + +static inline bool ascii_isalnum(unsigned char c) { + return (kAsciiPropertyBits[c] & 0x04) != 0; +} + +static inline bool ascii_isspace(unsigned char c) { + return (kAsciiPropertyBits[c] & 0x08) != 0; +} + +static inline bool ascii_ispunct(unsigned char c) { + return (kAsciiPropertyBits[c] & 0x10) != 0; +} + +static inline bool ascii_isblank(unsigned char c) { + return (kAsciiPropertyBits[c] & 0x20) != 0; +} + +static inline bool ascii_iscntrl(unsigned char c) { + return (kAsciiPropertyBits[c] & 0x40) != 0; +} + +static inline bool ascii_isxdigit(unsigned char c) { + return (kAsciiPropertyBits[c] & 0x80) != 0; +} + +static inline bool ascii_isdigit(unsigned char c) { + return c >= '0' && c <= '9'; +} + +static inline bool ascii_isprint(unsigned char c) { return c >= 32 && c < 127; } + +static inline bool ascii_isgraph(unsigned char c) { return c > 32 && c < 127; } + +static inline bool ascii_isupper(unsigned char c) { + return c >= 'A' && c <= 'Z'; +} + +static inline bool ascii_islower(unsigned char c) { + return c >= 'a' && c <= 'z'; +} + +static inline bool ascii_isascii(unsigned char c) { return c < 128; } + +extern const char kAsciiToLower[256]; +static inline char ascii_tolower(unsigned char c) { return kAsciiToLower[c]; } +extern const char kAsciiToUpper[256]; +static inline char ascii_toupper(unsigned char c) { return kAsciiToUpper[c]; } + +} // namespace dynamic_depth + +#endif // DYNAMIC_DEPTH_INTERNAL_STRINGS_ASCII_CTYPE_H_ // NOLINT diff --git a/internal/strings/case.cc b/internal/strings/case.cc new file mode 100644 index 0000000..6592988 --- /dev/null +++ b/internal/strings/case.cc @@ -0,0 +1,14 @@ +#include "strings/case.h" + +#include "strings/ascii_ctype.h" + +namespace dynamic_depth { + +void LowerString(string* s) { + string::iterator end = s->end(); + for (string::iterator i = s->begin(); i != end; ++i) { + *i = ascii_tolower(*i); + } +} + +} // namespace dynamic_depth diff --git a/internal/strings/case.h b/internal/strings/case.h new file mode 100644 index 0000000..8cf78fc --- /dev/null +++ b/internal/strings/case.h @@ -0,0 +1,44 @@ +// This file contains string processing functions related to +// uppercase, lowercase, etc. +// +// These functions are for ASCII only. If you need to process UTF8 strings, +// take a look at files in i18n/utf8. + +#ifndef DYNAMIC_DEPTH_INTERNAL_STRINGS_CASE_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_STRINGS_CASE_H_ // NOLINT + +#include <string> + +#include "base/port.h" + +namespace dynamic_depth { + +// Returns true if the two strings are equal, case-insensitively speaking. +// Uses C/POSIX locale. +inline bool StringCaseEqual(const string& s1, const string& s2) { + return strcasecmp(s1.c_str(), s2.c_str()) == 0; +} + +// ---------------------------------------------------------------------- +// LowerString() +// LowerStringToBuf() +// Convert the characters in "s" to lowercase. +// Works only with ASCII strings; for UTF8, see ToLower in +// util/utf8/public/unilib.h +// Changes contents of "s". LowerStringToBuf copies at most +// "n" characters (including the terminating '\0') from "s" +// to another buffer. +// ---------------------------------------------------------------------- +void LowerString(string* s); + +namespace strings { +inline string ToLower(const string& s) { + string out(s); + LowerString(&out); + return out; +} + +} // namespace strings +} // namespace dynamic_depth + +#endif // DYNAMIC_DEPTH_INTERNAL_STRINGS_CASE_H_ // NOLINT diff --git a/internal/strings/escaping.cc b/internal/strings/escaping.cc new file mode 100644 index 0000000..de8e297 --- /dev/null +++ b/internal/strings/escaping.cc @@ -0,0 +1,592 @@ +#include "strings/escaping.h" + +#include <cassert> + +#include "android-base/logging.h" +#include "strings/ascii_ctype.h" + +namespace dynamic_depth { +namespace absl { + +// ---------------------------------------------------------------------- +// ptrdiff_t Base64Unescape() - base64 decoder +// ptrdiff_t Base64Escape() - base64 encoder +// ptrdiff_t WebSafeBase64Unescape() - Google's variation of base64 decoder +// ptrdiff_t WebSafeBase64Escape() - Google's variation of base64 encoder +// +// Check out +// http://tools.ietf.org/html/rfc2045 for formal description, but what we +// care about is that... +// Take the encoded stuff in groups of 4 characters and turn each +// character into a code 0 to 63 thus: +// A-Z map to 0 to 25 +// a-z map to 26 to 51 +// 0-9 map to 52 to 61 +// +(- for WebSafe) maps to 62 +// /(_ for WebSafe) maps to 63 +// There will be four numbers, all less than 64 which can be represented +// by a 6 digit binary number (aaaaaa, bbbbbb, cccccc, dddddd respectively). +// Arrange the 6 digit binary numbers into three bytes as such: +// aaaaaabb bbbbcccc ccdddddd +// Equals signs (one or two) are used at the end of the encoded block to +// indicate that the text was not an integer multiple of three bytes long. +// ---------------------------------------------------------------------- + +bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, + size_t szdest, const signed char* unbase64, + size_t* len) { + static const char kPad64Equals = '='; + static const char kPad64Dot = '.'; + + size_t destidx = 0; + int decode = 0; + int state = 0; + unsigned int ch = 0; + unsigned int temp = 0; + + // If "char" is signed by default, using *src as an array index results in + // accessing negative array elements. Treat the input as a pointer to + // unsigned char to avoid this. + const unsigned char* src = reinterpret_cast<const unsigned char*>(src_param); + + // The GET_INPUT macro gets the next input character, skipping + // over any whitespace, and stopping when we reach the end of the + // string or when we read any non-data character. The arguments are + // an arbitrary identifier (used as a label for goto) and the number + // of data bytes that must remain in the input to avoid aborting the + // loop. +#define GET_INPUT(label, remain) \ + label: \ + --szsrc; \ + ch = *src++; \ + decode = unbase64[ch]; \ + if (decode < 0) { \ + if (ascii_isspace(ch) && szsrc >= remain) goto label; \ + state = 4 - remain; \ + break; \ + } + + // if dest is null, we're just checking to see if it's legal input + // rather than producing output. (I suspect this could just be done + // with a regexp...). We duplicate the loop so this test can be + // outside it instead of in every iteration. + + if (dest) { + // This loop consumes 4 input bytes and produces 3 output bytes + // per iteration. We can't know at the start that there is enough + // data left in the string for a full iteration, so the loop may + // break out in the middle; if so 'state' will be set to the + // number of input bytes read. + + while (szsrc >= 4) { + // We'll start by optimistically assuming that the next four + // bytes of the string (src[0..3]) are four good data bytes + // (that is, no nulls, whitespace, padding chars, or illegal + // chars). We need to test src[0..2] for nulls individually + // before constructing temp to preserve the property that we + // never read past a null in the string (no matter how long + // szsrc claims the string is). + + if (!src[0] || !src[1] || !src[2] || + ((temp = ((unsigned(unbase64[src[0]]) << 18) | + (unsigned(unbase64[src[1]]) << 12) | + (unsigned(unbase64[src[2]]) << 6) | + (unsigned(unbase64[src[3]])))) & + 0x80000000)) { + // Iff any of those four characters was bad (null, illegal, + // whitespace, padding), then temp's high bit will be set + // (because unbase64[] is -1 for all bad characters). + // + // We'll back up and resort to the slower decoder, which knows + // how to handle those cases. + + GET_INPUT(first, 4); + temp = decode; + GET_INPUT(second, 3); + temp = (temp << 6) | decode; + GET_INPUT(third, 2); + temp = (temp << 6) | decode; + GET_INPUT(fourth, 1); + temp = (temp << 6) | decode; + } else { + // We really did have four good data bytes, so advance four + // characters in the string. + + szsrc -= 4; + src += 4; + decode = -1; + ch = '\0'; + } + + // temp has 24 bits of input, so write that out as three bytes. + + if (destidx + 3 > szdest) return false; + dest[destidx + 2] = temp; + temp >>= 8; + dest[destidx + 1] = temp; + temp >>= 8; + dest[destidx] = temp; + destidx += 3; + } + } else { + while (szsrc >= 4) { + if (!src[0] || !src[1] || !src[2] || + ((temp = ((unsigned(unbase64[src[0]]) << 18) | + (unsigned(unbase64[src[1]]) << 12) | + (unsigned(unbase64[src[2]]) << 6) | + (unsigned(unbase64[src[3]])))) & + 0x80000000)) { + GET_INPUT(first_no_dest, 4); + GET_INPUT(second_no_dest, 3); + GET_INPUT(third_no_dest, 2); + GET_INPUT(fourth_no_dest, 1); + } else { + szsrc -= 4; + src += 4; + decode = -1; + ch = '\0'; + } + destidx += 3; + } + } + +#undef GET_INPUT + + // if the loop terminated because we read a bad character, return + // now. + if (decode < 0 && ch != '\0' && ch != kPad64Equals && ch != kPad64Dot && + !ascii_isspace(ch)) + return false; + + if (ch == kPad64Equals || ch == kPad64Dot) { + // if we stopped by hitting an '=' or '.', un-read that character -- we'll + // look at it again when we count to check for the proper number of + // equals signs at the end. + ++szsrc; + --src; + } else { + // This loop consumes 1 input byte per iteration. It's used to + // clean up the 0-3 input bytes remaining when the first, faster + // loop finishes. 'temp' contains the data from 'state' input + // characters read by the first loop. + while (szsrc > 0) { + --szsrc; + ch = *src++; + decode = unbase64[ch]; + if (decode < 0) { + if (ascii_isspace(ch)) { + continue; + } else if (ch == '\0') { + break; + } else if (ch == kPad64Equals || ch == kPad64Dot) { + // back up one character; we'll read it again when we check + // for the correct number of pad characters at the end. + ++szsrc; + --src; + break; + } else { + return false; + } + } + + // Each input character gives us six bits of output. + temp = (temp << 6) | decode; + ++state; + if (state == 4) { + // If we've accumulated 24 bits of output, write that out as + // three bytes. + if (dest) { + if (destidx + 3 > szdest) return false; + dest[destidx + 2] = temp; + temp >>= 8; + dest[destidx + 1] = temp; + temp >>= 8; + dest[destidx] = temp; + } + destidx += 3; + state = 0; + temp = 0; + } + } + } + + // Process the leftover data contained in 'temp' at the end of the input. + int expected_equals = 0; + switch (state) { + case 0: + // Nothing left over; output is a multiple of 3 bytes. + break; + + case 1: + // Bad input; we have 6 bits left over. + return false; + + case 2: + // Produce one more output byte from the 12 input bits we have left. + if (dest) { + if (destidx + 1 > szdest) return false; + temp >>= 4; + dest[destidx] = temp; + } + ++destidx; + expected_equals = 2; + break; + + case 3: + // Produce two more output bytes from the 18 input bits we have left. + if (dest) { + if (destidx + 2 > szdest) return false; + temp >>= 2; + dest[destidx + 1] = temp; + temp >>= 8; + dest[destidx] = temp; + } + destidx += 2; + expected_equals = 1; + break; + + default: + // state should have no other values at this point. + LOG(FATAL) << "This can't happen; base64 decoder state = " << state; + } + + // The remainder of the string should be all whitespace, mixed with + // exactly 0 equals signs, or exactly 'expected_equals' equals + // signs. (Always accepting 0 equals signs is a google extension + // not covered in the RFC, as is accepting dot as the pad character.) + + int equals = 0; + while (szsrc > 0 && *src) { + if (*src == kPad64Equals || *src == kPad64Dot) + ++equals; + else if (!ascii_isspace(*src)) + return false; + --szsrc; + ++src; + } + + const bool ok = (equals == 0 || equals == expected_equals); + if (ok) *len = destidx; + return ok; +} + +// The arrays below were generated by the following code +// #include <sys/time.h> +// #include <stdlib.h> +// #include <string.h> +// main() +// { +// static const char Base64[] = +// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +// char* pos; +// int idx, i, j; +// printf(" "); +// for (i = 0; i < 255; i += 8) { +// for (j = i; j < i + 8; j++) { +// pos = strchr(Base64, j); +// if ((pos == NULL) || (j == 0)) +// idx = -1; +// else +// idx = pos - Base64; +// if (idx == -1) +// printf(" %2d, ", idx); +// else +// printf(" %2d/*%c*/,", idx, j); +// } +// printf("\n "); +// } +// } +// +// where the value of "Base64[]" was replaced by one of the base-64 conversion +// tables from the functions below. +static const signed char kUnBase64[] = { + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, 62 /*+*/, -1, -1, -1, 63 /*/ */, 52 /*0*/, + 53 /*1*/, 54 /*2*/, 55 /*3*/, 56 /*4*/, 57 /*5*/, 58 /*6*/, 59 /*7*/, + 60 /*8*/, 61 /*9*/, -1, -1, -1, -1, -1, + -1, -1, 0 /*A*/, 1 /*B*/, 2 /*C*/, 3 /*D*/, 4 /*E*/, + 5 /*F*/, 6 /*G*/, 07 /*H*/, 8 /*I*/, 9 /*J*/, 10 /*K*/, 11 /*L*/, + 12 /*M*/, 13 /*N*/, 14 /*O*/, 15 /*P*/, 16 /*Q*/, 17 /*R*/, 18 /*S*/, + 19 /*T*/, 20 /*U*/, 21 /*V*/, 22 /*W*/, 23 /*X*/, 24 /*Y*/, 25 /*Z*/, + -1, -1, -1, -1, -1, -1, 26 /*a*/, + 27 /*b*/, 28 /*c*/, 29 /*d*/, 30 /*e*/, 31 /*f*/, 32 /*g*/, 33 /*h*/, + 34 /*i*/, 35 /*j*/, 36 /*k*/, 37 /*l*/, 38 /*m*/, 39 /*n*/, 40 /*o*/, + 41 /*p*/, 42 /*q*/, 43 /*r*/, 44 /*s*/, 45 /*t*/, 46 /*u*/, 47 /*v*/, + 48 /*w*/, 49 /*x*/, 50 /*y*/, 51 /*z*/, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1}; +static const signed char kUnWebSafeBase64[] = { + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 62 /*-*/, -1, -1, 52 /*0*/, + 53 /*1*/, 54 /*2*/, 55 /*3*/, 56 /*4*/, 57 /*5*/, 58 /*6*/, 59 /*7*/, + 60 /*8*/, 61 /*9*/, -1, -1, -1, -1, -1, + -1, -1, 0 /*A*/, 1 /*B*/, 2 /*C*/, 3 /*D*/, 4 /*E*/, + 5 /*F*/, 6 /*G*/, 07 /*H*/, 8 /*I*/, 9 /*J*/, 10 /*K*/, 11 /*L*/, + 12 /*M*/, 13 /*N*/, 14 /*O*/, 15 /*P*/, 16 /*Q*/, 17 /*R*/, 18 /*S*/, + 19 /*T*/, 20 /*U*/, 21 /*V*/, 22 /*W*/, 23 /*X*/, 24 /*Y*/, 25 /*Z*/, + -1, -1, -1, -1, 63 /*_*/, -1, 26 /*a*/, + 27 /*b*/, 28 /*c*/, 29 /*d*/, 30 /*e*/, 31 /*f*/, 32 /*g*/, 33 /*h*/, + 34 /*i*/, 35 /*j*/, 36 /*k*/, 37 /*l*/, 38 /*m*/, 39 /*n*/, 40 /*o*/, + 41 /*p*/, 42 /*q*/, 43 /*r*/, 44 /*s*/, 45 /*t*/, 46 /*u*/, 47 /*v*/, + 48 /*w*/, 49 /*x*/, 50 /*y*/, 51 /*z*/, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1}; + +static bool Base64UnescapeInternal(const char* src, size_t slen, string* dest, + const signed char* unbase64) { + // Determine the size of the output string. Base64 encodes every 3 bytes into + // 4 characters. any leftover chars are added directly for good measure. + // This is documented in the base64 RFC: http://tools.ietf.org/html/rfc3548 + const size_t dest_len = 3 * (slen / 4) + (slen % 4); + + dest->resize(dest_len); + + // We are getting the destination buffer by getting the beginning of the + // string and converting it into a char *. + size_t len; + const bool ok = + Base64UnescapeInternal(src, slen, dest->empty() ? NULL : &*dest->begin(), + dest_len, unbase64, &len); + if (!ok) { + dest->clear(); + return false; + } + + // could be shorter if there was padding + DCHECK_LE(len, dest_len); + dest->erase(len); + + return true; +} + +bool Base64Unescape(const string& src, string* dest) { + return Base64UnescapeInternal(src.data(), src.size(), dest, kUnBase64); +} + +bool WebSafeBase64Unescape(const string& src, string* dest) { + return Base64UnescapeInternal(src.data(), src.size(), dest, kUnWebSafeBase64); +} + +} // namespace absl + +namespace strings { + +// Base64Escape +// +// NOTE: We have to use an unsigned type for src because code built +// in the the /google tree treats characters as signed unless +// otherwised specified. +int Base64EscapeInternal(const unsigned char* src, int szsrc, char* dest, + int szdest, const char* base64, bool do_padding) { + static const char kPad64 = '='; + + if (szsrc <= 0) return 0; + + char* cur_dest = dest; + const unsigned char* cur_src = src; + + // Three bytes of data encodes to four characters of cyphertext. + // So we can pump through three-byte chunks atomically. + while (szsrc > 2) { /* keep going until we have less than 24 bits */ + if ((szdest -= 4) < 0) return 0; + cur_dest[0] = base64[cur_src[0] >> 2]; + cur_dest[1] = base64[((cur_src[0] & 0x03) << 4) + (cur_src[1] >> 4)]; + cur_dest[2] = base64[((cur_src[1] & 0x0f) << 2) + (cur_src[2] >> 6)]; + cur_dest[3] = base64[cur_src[2] & 0x3f]; + + cur_dest += 4; + cur_src += 3; + szsrc -= 3; + } + + /* now deal with the tail (<=2 bytes) */ + switch (szsrc) { + case 0: + // Nothing left; nothing more to do. + break; + case 1: + // One byte left: this encodes to two characters, and (optionally) + // two pad characters to round out the four-character cypherblock. + if ((szdest -= 2) < 0) return 0; + cur_dest[0] = base64[cur_src[0] >> 2]; + cur_dest[1] = base64[(cur_src[0] & 0x03) << 4]; + cur_dest += 2; + if (do_padding) { + if ((szdest -= 2) < 0) return 0; + cur_dest[0] = kPad64; + cur_dest[1] = kPad64; + cur_dest += 2; + } + break; + case 2: + // Two bytes left: this encodes to three characters, and (optionally) + // one pad character to round out the four-character cypherblock. + if ((szdest -= 3) < 0) return 0; + cur_dest[0] = base64[cur_src[0] >> 2]; + cur_dest[1] = base64[((cur_src[0] & 0x03) << 4) + (cur_src[1] >> 4)]; + cur_dest[2] = base64[(cur_src[1] & 0x0f) << 2]; + cur_dest += 3; + if (do_padding) { + if ((szdest -= 1) < 0) return 0; + cur_dest[0] = kPad64; + cur_dest += 1; + } + break; + default: + // Should not be reached: blocks of 3 bytes are handled + // in the while loop before this switch statement. + CHECK(false) << "Logic problem? szsrc = " << szsrc; + break; + } + return (cur_dest - dest); +} + +static const char kBase64Chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// Digit conversion. +static const char kHexTable[513] = + "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + +size_t CalculateBase64EscapedLenInternal(size_t input_len, bool do_padding) { + // Base64 encodes three bytes of input at a time. If the input is not + // divisible by three, we pad as appropriate. + // + // (from http://tools.ietf.org/html/rfc3548) + // Special processing is performed if fewer than 24 bits are available + // at the end of the data being encoded. A full encoding quantum is + // always completed at the end of a quantity. When fewer than 24 input + // bits are available in an input group, zero bits are added (on the + // right) to form an integral number of 6-bit groups. Padding at the + // end of the data is performed using the '=' character. Since all base + // 64 input is an integral number of octets, only the following cases + // can arise: + + // Base64 encodes each three bytes of input into four bytes of output. + size_t len = (input_len / 3) * 4; + + if (input_len % 3 == 0) { + // (from http://tools.ietf.org/html/rfc3548) + // (1) the final quantum of encoding input is an integral multiple of 24 + // bits; here, the final unit of encoded output will be an integral + // multiple of 4 characters with no "=" padding, + } else if (input_len % 3 == 1) { + // (from http://tools.ietf.org/html/rfc3548) + // (2) the final quantum of encoding input is exactly 8 bits; here, the + // final unit of encoded output will be two characters followed by two + // "=" padding characters, or + len += 2; + if (do_padding) { + len += 2; + } + } else { // (input_len % 3 == 2) + // (from http://tools.ietf.org/html/rfc3548) + // (3) the final quantum of encoding input is exactly 16 bits; here, the + // final unit of encoded output will be three characters followed by one + // "=" padding character. + len += 3; + if (do_padding) { + len += 1; + } + } + + assert(len >= input_len); // make sure we didn't overflow + return len; +} + +void Base64EscapeInternal(const unsigned char* src, size_t szsrc, string* dest, + bool do_padding, const char* base64_chars) { + const size_t calc_escaped_size = + CalculateBase64EscapedLenInternal(szsrc, do_padding); + dest->resize(calc_escaped_size); + const int escaped_len = Base64EscapeInternal( + src, static_cast<int>(szsrc), dest->empty() ? NULL : &*dest->begin(), + static_cast<int>(dest->size()), base64_chars, do_padding); + DCHECK_EQ(calc_escaped_size, escaped_len); + dest->erase(escaped_len); +} + +void Base64Escape(const unsigned char* src, ptrdiff_t szsrc, string* dest, + bool do_padding) { + if (szsrc < 0) return; + Base64EscapeInternal(src, szsrc, dest, do_padding, kBase64Chars); +} + +// This is a templated function so that T can be either a char* or a string. +template <typename T> +static void b2a_hex_t(const unsigned char* src, T dest, ptrdiff_t num) { + auto dest_ptr = &dest[0]; + for (auto src_ptr = src; src_ptr != (src + num); ++src_ptr, dest_ptr += 2) { + const char* hex_p = &kHexTable[*src_ptr * 2]; + std::copy(hex_p, hex_p + 2, dest_ptr); + } +} + +string b2a_hex(const char* b, ptrdiff_t len) { + string result; + result.resize(len << 1); + b2a_hex_t<string&>(reinterpret_cast<const unsigned char*>(b), result, len); + return result; +} + +} // namespace strings +} // namespace dynamic_depth diff --git a/internal/strings/escaping.h b/internal/strings/escaping.h new file mode 100644 index 0000000..b0316b1 --- /dev/null +++ b/internal/strings/escaping.h @@ -0,0 +1,72 @@ +// This is a grab-bag file for string utilities involved in escaping and +// unescaping strings in various ways. Who knew there were so many? +// +// There are more escaping functions in: +// webutil/html/tagutils.h (Escaping strings for HTML, PRE, JavaScript, etc.) +// webutil/url/url.h (Escaping for URL's, both RFC-2396 and other methods) +// template/template_modifiers.h (All sorts of stuff) +// util/regexp/re2/re2.h (Escaping for literals within regular expressions +// - see RE2::QuoteMeta). +// And probably many more places, as well. + +#ifndef DYNAMIC_DEPTH_INTERNAL_STRINGS_ESCAPING_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_STRINGS_ESCAPING_H_ // NOLINT + +#include <stddef.h> // For ptrdiff_t. + +#include <string> + +#include "base/port.h" + +namespace dynamic_depth { +namespace absl { + +// ---------------------------------------------------------------------- +// Base64Unescape() +// Converts "src" which is encoded in Base64 to its binary equivalent and +// writes it to "dest". If src contains invalid characters, dest is cleared +// and the function returns false. Returns true on success. +// ---------------------------------------------------------------------- +bool Base64Unescape(const string& src, string* dest); + +// ---------------------------------------------------------------------- +// WebSafeBase64Unescape() +// This is a variation of Base64Unescape which uses '-' instead of '+', and +// '_' instead of '/'. src is not null terminated, instead specify len. I +// recommend that slen<szdest, but we honor szdest anyway. +// RETURNS the length of dest, or -1 if there's an error. + +// The variation that stores into a string clears the string first, and +// returns false (with dest empty) if src contains invalid chars; for +// this version src and dest must be different strings. +// ---------------------------------------------------------------------- +bool WebSafeBase64Unescape(const string& src, string* dest); + +} // namespace absl + +namespace strings { + +// ---------------------------------------------------------------------- +// Base64Escape() +// Encode "src" to "dest" using base64 encoding. +// src is not null terminated, instead specify len. +// 'dest' should have at least CalculateBase64EscapedLen() length. +// RETURNS the length of dest. +// It also has an extra parameter "do_padding", +// which when set to false will prevent padding with "=". +// ---------------------------------------------------------------------- +void Base64Escape(const unsigned char* src, ptrdiff_t szsrc, string* dest, + bool do_padding); + +// ---------------------------------------------------------------------- +// b2a_hex() +// Description: Binary-to-Ascii hex conversion. This converts +// 'num' bytes of binary to a 2*'num'-character hexadecimal representation +// Return value: 2*'num' characters of ascii string +// ---------------------------------------------------------------------- +string b2a_hex(const char* from, ptrdiff_t num); + +} // namespace strings +} // namespace dynamic_depth + +#endif // DYNAMIC_DEPTH_INTERNAL_STRINGS_ESCAPING_H_ // NOLINT diff --git a/internal/strings/fastmem.h b/internal/strings/fastmem.h new file mode 100644 index 0000000..083ed47 --- /dev/null +++ b/internal/strings/fastmem.h @@ -0,0 +1,183 @@ +// Fast memory copying and comparison routines. +// strings::fastmemcmp_inlined() replaces memcmp() +// strings::memcpy_inlined() replaces memcpy() +// strings::memeq(a, b, n) replaces memcmp(a, b, n) == 0 +// +// strings::*_inlined() routines are inline versions of the +// routines exported by this module. Sometimes using the inlined +// versions is faster. Measure before using the inlined versions. + +#ifndef DYNAMIC_DEPTH_INTERNAL_STRINGS_FASTMEM_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_STRINGS_FASTMEM_H_ // NOLINT + +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "base/integral_types.h" +#include "base/macros.h" +#include "base/port.h" + +namespace dynamic_depth { +namespace strings { + +// Return true if the n bytes at a equal the n bytes at b. +// The regions are allowed to overlap. +// +// The performance is similar to the performance of memcmp(), but faster for +// moderately-sized inputs, or inputs that share a common prefix and differ +// somewhere in their last 8 bytes. Further optimizations can be added later +// if it makes sense to do so. Alternatively, if the compiler & runtime improve +// to eliminate the need for this, we can remove it. Please keep this in sync +// with google_internal::gg_memeq() in //third_party/stl/gcc3/string. +inline bool memeq(const char* a, const char* b, size_t n) { + size_t n_rounded_down = n & ~static_cast<size_t>(7); + if (PREDICT_FALSE(n_rounded_down == 0)) { // n <= 7 + return memcmp(a, b, n) == 0; + } + // n >= 8 + uint64 u = UNALIGNED_LOAD64(a) ^ UNALIGNED_LOAD64(b); + uint64 v = UNALIGNED_LOAD64(a + n - 8) ^ UNALIGNED_LOAD64(b + n - 8); + if ((u | v) != 0) { // The first or last 8 bytes differ. + return false; + } + // The next line forces n to be a multiple of 8. + n = n_rounded_down; + if (n >= 80) { + // In 2013 or later, this should be fast on long strings. + return memcmp(a, b, n) == 0; + } + // Now force n to be a multiple of 16. Arguably, a "switch" would be smart + // here, but there's a difficult-to-evaluate code size vs. speed issue. The + // current approach often re-compares some bytes (worst case is if n initially + // was 16, 32, 48, or 64), but is fairly short. + size_t e = n & 8; + a += e; + b += e; + n -= e; + // n is now in {0, 16, 32, ...}. Process 0 or more 16-byte chunks. + while (n > 0) { + uint64 x = UNALIGNED_LOAD64(a) ^ UNALIGNED_LOAD64(b); + uint64 y = UNALIGNED_LOAD64(a + 8) ^ UNALIGNED_LOAD64(b + 8); + if ((x | y) != 0) { + return false; + } + a += 16; + b += 16; + n -= 16; + } + return true; +} + +inline int fastmemcmp_inlined(const void* va, const void* vb, size_t n) { + const unsigned char* pa = static_cast<const unsigned char*>(va); + const unsigned char* pb = static_cast<const unsigned char*>(vb); + switch (n) { + default: + return memcmp(va, vb, n); + case 7: + if (*pa != *pb) return *pa < *pb ? -1 : +1; + ++pa; + ++pb; + FALLTHROUGH_INTENDED; + case 6: + if (*pa != *pb) return *pa < *pb ? -1 : +1; + ++pa; + ++pb; + FALLTHROUGH_INTENDED; + case 5: + if (*pa != *pb) return *pa < *pb ? -1 : +1; + ++pa; + ++pb; + FALLTHROUGH_INTENDED; + case 4: + if (*pa != *pb) return *pa < *pb ? -1 : +1; + ++pa; + ++pb; + FALLTHROUGH_INTENDED; + case 3: + if (*pa != *pb) return *pa < *pb ? -1 : +1; + ++pa; + ++pb; + FALLTHROUGH_INTENDED; + case 2: + if (*pa != *pb) return *pa < *pb ? -1 : +1; + ++pa; + ++pb; + FALLTHROUGH_INTENDED; + case 1: + if (*pa != *pb) return *pa < *pb ? -1 : +1; + FALLTHROUGH_INTENDED; + case 0: + break; + } + return 0; +} + +// The standard memcpy operation is slow for variable small sizes. +// This implementation inlines the optimal realization for sizes 1 to 16. +// To avoid code bloat don't use it in case of not performance-critical spots, +// nor when you don't expect very frequent values of size <= 16. +inline void memcpy_inlined(char* dst, const char* src, size_t size) { + // Compiler inlines code with minimal amount of data movement when third + // parameter of memcpy is a constant. + switch (size) { + case 1: + memcpy(dst, src, 1); + break; + case 2: + memcpy(dst, src, 2); + break; + case 3: + memcpy(dst, src, 3); + break; + case 4: + memcpy(dst, src, 4); + break; + case 5: + memcpy(dst, src, 5); + break; + case 6: + memcpy(dst, src, 6); + break; + case 7: + memcpy(dst, src, 7); + break; + case 8: + memcpy(dst, src, 8); + break; + case 9: + memcpy(dst, src, 9); + break; + case 10: + memcpy(dst, src, 10); + break; + case 11: + memcpy(dst, src, 11); + break; + case 12: + memcpy(dst, src, 12); + break; + case 13: + memcpy(dst, src, 13); + break; + case 14: + memcpy(dst, src, 14); + break; + case 15: + memcpy(dst, src, 15); + break; + case 16: + memcpy(dst, src, 16); + break; + default: + memcpy(dst, src, size); + break; + } +} + +} // namespace strings +} // namespace dynamic_depth + +#endif // DYNAMIC_DEPTH_INTERNAL_STRINGS_FASTMEM_H_ // NOLINT diff --git a/internal/strings/numbers.cc b/internal/strings/numbers.cc new file mode 100644 index 0000000..b85ed8e --- /dev/null +++ b/internal/strings/numbers.cc @@ -0,0 +1,546 @@ +#include "strings/numbers.h" + +#include <float.h> // for FLT_DIG +#include <cassert> +#include <memory> + +#include "strings/ascii_ctype.h" + +namespace dynamic_depth { +namespace strings { +namespace { + +// Represents integer values of digits. +// Uses 36 to indicate an invalid character since we support +// bases up to 36. +static const int8 kAsciiToInt[256] = { + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, // 16 36s. + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 36, 36, 36, 36, 36, 36, 36, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 36, 36, 36, 36, 36, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36}; + +// Parse the sign and optional hex or oct prefix in text. +inline bool safe_parse_sign_and_base(string* text /*inout*/, + int* base_ptr /*inout*/, + bool* negative_ptr /*output*/) { + if (text->data() == NULL) { + return false; + } + + const char* start = text->data(); + const char* end = start + text->size(); + int base = *base_ptr; + + // Consume whitespace. + while (start < end && ascii_isspace(start[0])) { + ++start; + } + while (start < end && ascii_isspace(end[-1])) { + --end; + } + if (start >= end) { + return false; + } + + // Consume sign. + *negative_ptr = (start[0] == '-'); + if (*negative_ptr || start[0] == '+') { + ++start; + if (start >= end) { + return false; + } + } + + // Consume base-dependent prefix. + // base 0: "0x" -> base 16, "0" -> base 8, default -> base 10 + // base 16: "0x" -> base 16 + // Also validate the base. + if (base == 0) { + if (end - start >= 2 && start[0] == '0' && + (start[1] == 'x' || start[1] == 'X')) { + base = 16; + start += 2; + if (start >= end) { + // "0x" with no digits after is invalid. + return false; + } + } else if (end - start >= 1 && start[0] == '0') { + base = 8; + start += 1; + } else { + base = 10; + } + } else if (base == 16) { + if (end - start >= 2 && start[0] == '0' && + (start[1] == 'x' || start[1] == 'X')) { + start += 2; + if (start >= end) { + // "0x" with no digits after is invalid. + return false; + } + } + } else if (base >= 2 && base <= 36) { + // okay + } else { + return false; + } + text->assign(start, end - start); + *base_ptr = base; + return true; +} + +// Consume digits. +// +// The classic loop: +// +// for each digit +// value = value * base + digit +// value *= sign +// +// The classic loop needs overflow checking. It also fails on the most +// negative integer, -2147483648 in 32-bit two's complement representation. +// +// My improved loop: +// +// if (!negative) +// for each digit +// value = value * base +// value = value + digit +// else +// for each digit +// value = value * base +// value = value - digit +// +// Overflow checking becomes simple. + +// Lookup tables per IntType: +// vmax/base and vmin/base are precomputed because division costs at least 8ns. +// TODO(junyer): Doing this per base instead (i.e. an array of structs, not a +// struct of arrays) would probably be better in terms of d-cache for the most +// commonly used bases. +template <typename IntType> +struct LookupTables { + static const IntType kVmaxOverBase[]; + static const IntType kVminOverBase[]; +}; + +// An array initializer macro for X/base where base in [0, 36]. +// However, note that lookups for base in [0, 1] should never happen because +// base has been validated to be in [2, 36] by safe_parse_sign_and_base(). +#define X_OVER_BASE_INITIALIZER(X) \ + { \ + 0, 0, X / 2, X / 3, X / 4, X / 5, X / 6, X / 7, \ + X / 8, X / 9, X / 10, X / 11, X / 12, X / 13, X / 14, X / 15, \ + X / 16, X / 17, X / 18, X / 19, X / 20, X / 21, X / 22, X / 23, \ + X / 24, X / 25, X / 26, X / 27, X / 28, X / 29, X / 30, X / 31, \ + X / 32, X / 33, X / 34, X / 35, X / 36, \ + }; + +template <typename IntType> +const IntType LookupTables<IntType>::kVmaxOverBase[] = + X_OVER_BASE_INITIALIZER(std::numeric_limits<IntType>::max()); + +template <typename IntType> +const IntType LookupTables<IntType>::kVminOverBase[] = + X_OVER_BASE_INITIALIZER(std::numeric_limits<IntType>::min()); + +#undef X_OVER_BASE_INITIALIZER + +template <typename IntType> +inline bool safe_parse_positive_int(const string& text, int base, + IntType* value_p) { + IntType value = 0; + const IntType vmax = std::numeric_limits<IntType>::max(); + assert(vmax > 0); + assert(vmax >= base); + const IntType vmax_over_base = LookupTables<IntType>::kVmaxOverBase[base]; + const char* start = text.data(); + const char* end = start + text.size(); + // loop over digits + for (; start < end; ++start) { + unsigned char c = static_cast<unsigned char>(start[0]); + int digit = kAsciiToInt[c]; + if (digit >= base) { + *value_p = value; + return false; + } + if (value > vmax_over_base) { + *value_p = vmax; + return false; + } + value *= base; + if (value > vmax - digit) { + *value_p = vmax; + return false; + } + value += digit; + } + *value_p = value; + return true; +} + +template <typename IntType> +inline bool safe_parse_negative_int(const string& text, int base, + IntType* value_p) { + IntType value = 0; + const IntType vmin = std::numeric_limits<IntType>::min(); + assert(vmin < 0); + assert(vmin <= 0 - base); + IntType vmin_over_base = LookupTables<IntType>::kVminOverBase[base]; + // 2003 c++ standard [expr.mul] + // "... the sign of the remainder is implementation-defined." + // Although (vmin/base)*base + vmin%base is always vmin. + // 2011 c++ standard tightens the spec but we cannot rely on it. + // TODO(junyer): Handle this in the lookup table generation. + if (vmin % base > 0) { + vmin_over_base += 1; + } + const char* start = text.data(); + const char* end = start + text.size(); + // loop over digits + for (; start < end; ++start) { + unsigned char c = static_cast<unsigned char>(start[0]); + int digit = kAsciiToInt[c]; + if (digit >= base) { + *value_p = value; + return false; + } + if (value < vmin_over_base) { + *value_p = vmin; + return false; + } + value *= base; + if (value < vmin + digit) { + *value_p = vmin; + return false; + } + value -= digit; + } + *value_p = value; + return true; +} + +// Input format based on POSIX.1-2008 strtol +// http://pubs.opengroup.org/onlinepubs/9699919799/functions/strtol.html +template <typename IntType> +inline bool safe_int_internal(const string& text, IntType* value_p, int base) { + *value_p = 0; + bool negative; + string text_copy(text); + if (!safe_parse_sign_and_base(&text_copy, &base, &negative)) { + return false; + } + if (!negative) { + return safe_parse_positive_int(text_copy, base, value_p); + } else { + return safe_parse_negative_int(text_copy, base, value_p); + } +} + +template <typename IntType> +inline bool safe_uint_internal(const string& text, IntType* value_p, int base) { + *value_p = 0; + bool negative; + string text_copy(text); + if (!safe_parse_sign_and_base(&text_copy, &base, &negative) || negative) { + return false; + } + return safe_parse_positive_int(text_copy, base, value_p); +} + +// Writes a two-character representation of 'i' to 'buf'. 'i' must be in the +// range 0 <= i < 100, and buf must have space for two characters. Example: +// char buf[2]; +// PutTwoDigits(42, buf); +// // buf[0] == '4' +// // buf[1] == '2' +inline void PutTwoDigits(size_t i, char* buf) { + static const char two_ASCII_digits[100][2] = { + {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, + {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, + {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, + {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, + {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, + {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, + {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'}, + {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, + {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, + {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, + {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, + {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'}, + {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, + {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, + {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, + {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, + {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; + assert(i < 100); + memcpy(buf, two_ASCII_digits[i], 2); +} + +} // anonymous namespace + +// ---------------------------------------------------------------------- +// FastInt32ToBufferLeft() +// FastUInt32ToBufferLeft() +// FastInt64ToBufferLeft() +// FastUInt64ToBufferLeft() +// +// Like the Fast*ToBuffer() functions above, these are intended for speed. +// Unlike the Fast*ToBuffer() functions, however, these functions write +// their output to the beginning of the buffer (hence the name, as the +// output is left-aligned). The caller is responsible for ensuring that +// the buffer has enough space to hold the output. +// +// Returns a pointer to the end of the string (i.e. the null character +// terminating the string). +// ---------------------------------------------------------------------- + +// Used to optimize printing a decimal number's final digit. +const char one_ASCII_final_digits[10][2]{ + {'0', 0}, {'1', 0}, {'2', 0}, {'3', 0}, {'4', 0}, + {'5', 0}, {'6', 0}, {'7', 0}, {'8', 0}, {'9', 0}, +}; + +char* FastUInt32ToBufferLeft(uint32 u, char* buffer) { + uint32 digits; + // The idea of this implementation is to trim the number of divides to as few + // as possible, and also reducing memory stores and branches, by going in + // steps of two digits at a time rather than one whenever possible. + // The huge-number case is first, in the hopes that the compiler will output + // that case in one branch-free block of code, and only output conditional + // branches into it from below. + if (u >= 1000000000) { // >= 1,000,000,000 + digits = u / 100000000; // 100,000,000 + u -= digits * 100000000; + PutTwoDigits(digits, buffer); + buffer += 2; + lt100_000_000: + digits = u / 1000000; // 1,000,000 + u -= digits * 1000000; + PutTwoDigits(digits, buffer); + buffer += 2; + lt1_000_000: + digits = u / 10000; // 10,000 + u -= digits * 10000; + PutTwoDigits(digits, buffer); + buffer += 2; + lt10_000: + digits = u / 100; + u -= digits * 100; + PutTwoDigits(digits, buffer); + buffer += 2; + lt100: + digits = u; + PutTwoDigits(digits, buffer); + buffer += 2; + *buffer = 0; + return buffer; + } + + if (u < 100) { + digits = u; + if (u >= 10) goto lt100; + memcpy(buffer, one_ASCII_final_digits[u], 2); + return buffer + 1; + } + if (u < 10000) { // 10,000 + if (u >= 1000) goto lt10_000; + digits = u / 100; + u -= digits * 100; + *buffer++ = '0' + digits; + goto lt100; + } + if (u < 1000000) { // 1,000,000 + if (u >= 100000) goto lt1_000_000; + digits = u / 10000; // 10,000 + u -= digits * 10000; + *buffer++ = '0' + digits; + goto lt10_000; + } + if (u < 100000000) { // 100,000,000 + if (u >= 10000000) goto lt100_000_000; + digits = u / 1000000; // 1,000,000 + u -= digits * 1000000; + *buffer++ = '0' + digits; + goto lt1_000_000; + } + // we already know that u < 1,000,000,000 + digits = u / 100000000; // 100,000,000 + u -= digits * 100000000; + *buffer++ = '0' + digits; + goto lt100_000_000; +} + +char* FastInt32ToBufferLeft(int32 i, char* buffer) { + uint32 u = i; + if (i < 0) { + *buffer++ = '-'; + // We need to do the negation in modular (i.e., "unsigned") + // arithmetic; MSVC++ apprently warns for plain "-u", so + // we write the equivalent expression "0 - u" instead. + u = 0 - u; + } + return FastUInt32ToBufferLeft(u, buffer); +} + +char* FastUInt64ToBufferLeft(uint64 u64, char* buffer) { + uint32 u32 = static_cast<uint32>(u64); + if (u32 == u64) return FastUInt32ToBufferLeft(u32, buffer); + + // Here we know u64 has at least 10 decimal digits. + uint64 top_1to11 = u64 / 1000000000; + u32 = static_cast<uint32>(u64 - top_1to11 * 1000000000); + uint32 top_1to11_32 = static_cast<uint32>(top_1to11); + + if (top_1to11_32 == top_1to11) { + buffer = FastUInt32ToBufferLeft(top_1to11_32, buffer); + } else { + // top_1to11 has more than 32 bits too; print it in two steps. + uint32 top_8to9 = static_cast<uint32>(top_1to11 / 100); + uint32 mid_2 = static_cast<uint32>(top_1to11 - top_8to9 * 100); + buffer = FastUInt32ToBufferLeft(top_8to9, buffer); + PutTwoDigits(mid_2, buffer); + buffer += 2; + } + + // We have only 9 digits now, again the maximum uint32 can handle fully. + uint32 digits = u32 / 10000000; // 10,000,000 + u32 -= digits * 10000000; + PutTwoDigits(digits, buffer); + buffer += 2; + digits = u32 / 100000; // 100,000 + u32 -= digits * 100000; + PutTwoDigits(digits, buffer); + buffer += 2; + digits = u32 / 1000; // 1,000 + u32 -= digits * 1000; + PutTwoDigits(digits, buffer); + buffer += 2; + digits = u32 / 10; + u32 -= digits * 10; + PutTwoDigits(digits, buffer); + buffer += 2; + memcpy(buffer, one_ASCII_final_digits[u32], 2); + return buffer + 1; +} + +char* FastInt64ToBufferLeft(int64 i, char* buffer) { + uint64 u = i; + if (i < 0) { + *buffer++ = '-'; + u = 0 - u; + } + return FastUInt64ToBufferLeft(u, buffer); +} + +bool safe_strto32_base(const string& text, int32* value, int base) { + return safe_int_internal<int32>(text, value, base); +} + +bool safe_strto64_base(const string& text, int64* value, int base) { + return safe_int_internal<int64>(text, value, base); +} + +bool safe_strtou32_base(const string& text, uint32* value, int base) { + return safe_uint_internal<uint32>(text, value, base); +} + +bool safe_strtou64_base(const string& text, uint64* value, int base) { + return safe_uint_internal<uint64>(text, value, base); +} + +bool safe_strtof(const string& piece, float* value) { + *value = 0.0; + if (piece.empty()) return false; + char buf[32]; + std::unique_ptr<char[]> bigbuf; + char* str = buf; + if (piece.size() > sizeof(buf) - 1) { + bigbuf.reset(new char[piece.size() + 1]); + str = bigbuf.get(); + } + memcpy(str, piece.data(), piece.size()); + str[piece.size()] = '\0'; + + char* endptr; +#ifdef COMPILER_MSVC // has no strtof() + *value = strtod(str, &endptr); +#else + *value = strtof(str, &endptr); +#endif + if (endptr != str) { + while (ascii_isspace(*endptr)) ++endptr; + } + // Ignore range errors from strtod/strtof. + // The values it returns on underflow and + // overflow are the right fallback in a + // robust setting. + return *str != '\0' && *endptr == '\0'; +} + +bool safe_strtod(const string& piece, double* value) { + *value = 0.0; + if (piece.empty()) return false; + char buf[32]; + std::unique_ptr<char[]> bigbuf; + char* str = buf; + if (piece.size() > sizeof(buf) - 1) { + bigbuf.reset(new char[piece.size() + 1]); + str = bigbuf.get(); + } + memcpy(str, piece.data(), piece.size()); + str[piece.size()] = '\0'; + + char* endptr; + *value = strtod(str, &endptr); + if (endptr != str) { + while (ascii_isspace(*endptr)) ++endptr; + } + // Ignore range errors from strtod. The values it + // returns on underflow and overflow are the right + // fallback in a robust setting. + return *str != '\0' && *endptr == '\0'; +} + +string SimpleFtoa(float value) { + char buffer[kFastToBufferSize]; + return FloatToBuffer(value, buffer); +} + +char* FloatToBuffer(float value, char* buffer) { + // FLT_DIG is 6 for IEEE-754 floats, which are used on almost all + // platforms these days. Just in case some system exists where FLT_DIG + // is significantly larger -- and risks overflowing our buffer -- we have + // this assert. + assert(FLT_DIG < 10); + + int snprintf_result = + snprintf(buffer, kFastToBufferSize, "%.*g", FLT_DIG, value); + + // The snprintf should never overflow because the buffer is significantly + // larger than the precision we asked for. + assert(snprintf_result > 0 && snprintf_result < kFastToBufferSize); + + float parsed_value; + if (!safe_strtof(buffer, &parsed_value) || parsed_value != value) { + snprintf_result = + snprintf(buffer, kFastToBufferSize, "%.*g", FLT_DIG + 2, value); + + // Should never overflow; see above. + assert(snprintf_result > 0 && snprintf_result < kFastToBufferSize); + } + return buffer; +} + +} // namespace strings +} // namespace dynamic_depth diff --git a/internal/strings/numbers.h b/internal/strings/numbers.h new file mode 100644 index 0000000..fa47acd --- /dev/null +++ b/internal/strings/numbers.h @@ -0,0 +1,168 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_STRINGS_NUMBERS_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_STRINGS_NUMBERS_H_ // NOLINT + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <functional> +#include <limits> +#include <string> + +#include "base/integral_types.h" +#include "base/port.h" +#include "strings/ascii_ctype.h" + +namespace dynamic_depth { +namespace strings { + +// Convert strings to numeric values, with strict error checking. +// Leading and trailing spaces are allowed. +// Negative inputs are not allowed for unsigned ints (unlike strtoul). +// +// Base must be [0, 2-36]. +// Base 0: +// auto-select base from first two chars: +// "0x" -> hex +// "0" -> octal +// else -> decimal +// Base 16: +// Number can start with "0x" +// +// On error, returns false, and sets *value to: +// std::numeric_limits<T>::max() on overflow +// std::numeric_limits<T>::min() on underflow +// conversion of leading substring if available ("123@@@" -> 123) +// 0 if no leading substring available +// The effect on errno is unspecified. +// Do not depend on testing errno. +bool safe_strto32_base(const string& text, int32* value, int base); +bool safe_strto64_base(const string& text, int64* value, int base); +bool safe_strtou32_base(const string& text, uint32* value, int base); +bool safe_strtou64_base(const string& text, uint64* value, int base); +bool safe_strtosize_t_base(const string& text, size_t* value, int base); + +// Convenience functions with base == 10. +inline bool safe_strto32(const string& text, int32* value) { + return safe_strto32_base(text, value, 10); +} + +inline bool safe_strto64(const string& text, int64* value) { + return safe_strto64_base(text, value, 10); +} + +inline bool safe_strtou32(const string& text, uint32* value) { + return safe_strtou32_base(text, value, 10); +} + +inline bool safe_strtou64(const string& text, uint64* value) { + return safe_strtou64_base(text, value, 10); +} + +// Convert strings to floating point values. +// Leading and trailing spaces are allowed. +// Values may be rounded on over- and underflow. +bool safe_strtof(const string& str, float* value); + +bool safe_strtod(const string& str, double* value); + +// Previously documented minimums -- the buffers provided must be at least this +// long, though these numbers are subject to change: +// Int32, UInt32: 12 bytes +// Int64, UInt64, Int, Uint: 22 bytes +// Time: 30 bytes +// Use kFastToBufferSize rather than hardcoding constants. +static const int kFastToBufferSize = 32; + +// ---------------------------------------------------------------------- +// FastInt32ToBufferLeft() +// FastUInt32ToBufferLeft() +// FastInt64ToBufferLeft() +// FastUInt64ToBufferLeft() +// +// Like the Fast*ToBuffer() functions above, these are intended for speed. +// Unlike the Fast*ToBuffer() functions, however, these functions write +// their output to the beginning of the buffer (hence the name, as the +// output is left-aligned). The caller is responsible for ensuring that +// the buffer has enough space to hold the output. +// +// Returns a pointer to the end of the string (i.e. the null character +// terminating the string). +// ---------------------------------------------------------------------- + +char* FastInt32ToBufferLeft(int32 i, char* buffer); // at least 12 bytes +char* FastUInt32ToBufferLeft(uint32 i, char* buffer); // at least 12 bytes +char* FastInt64ToBufferLeft(int64 i, char* buffer); // at least 22 bytes +char* FastUInt64ToBufferLeft(uint64 i, char* buffer); // at least 22 bytes + +// ---------------------------------------------------------------------- +// SimpleFtoa() +// Description: converts a double or float to a string which, if passed to +// strtod() or strtof() respectively, will produce the exact same original +// double or float. Exception: for NaN values, strtod(SimpleDtoa(NaN)) or +// strtof(SimpleFtoa(NaN)) may produce any NaN value, not necessarily the +// exact same original NaN value. +// +// The output string is not guaranteed to be as short as possible. +// +// The output string, including terminating NUL, will have length +// less than or equal to kFastToBufferSize defined above. Of course, +// we would prefer that your code not depend on this property of +// the output string. This guarantee derives from a similar guarantee +// from the previous generation of char-buffer-based functions. +// We had to carry it forward to preserve compatibility. +// ---------------------------------------------------------------------- +string SimpleFtoa(float value); + +// ---------------------------------------------------------------------- +// SimpleItoa() +// Description: converts an integer to a string. +// Faster than printf("%d"). +// +// Return value: string +// ---------------------------------------------------------------------- +inline string SimpleItoa(int32 i) { + char buf[16]; // Longest is -2147483648 + return string(buf, FastInt32ToBufferLeft(i, buf)); +} + +// We need this overload because otherwise SimpleItoa(5U) wouldn't compile. +inline string SimpleItoa(uint32 i) { + char buf[16]; // Longest is 4294967295 + return string(buf, FastUInt32ToBufferLeft(i, buf)); +} + +inline string SimpleItoa(int64 i) { + char buf[32]; // Longest is -9223372036854775808 + return string(buf, FastInt64ToBufferLeft(i, buf)); +} + +// We need this overload because otherwise SimpleItoa(5ULL) wouldn't compile. +inline string SimpleItoa(uint64 i) { + char buf[32]; // Longest is 18446744073709551615 + return string(buf, FastUInt64ToBufferLeft(i, buf)); +} + +inline string SimpleItoa(long i) { // NOLINT long is OK here + if (sizeof(i) == 64 / 8) { + return SimpleItoa(static_cast<int64>(i)); + } else if (sizeof(i) == 32 / 8) { + return SimpleItoa(static_cast<int32>(i)); + } +} + +inline string SimpleItoa(unsigned long i) { // NOLINT long is OK here + if (sizeof(i) == 64 / 8) { + return SimpleItoa(static_cast<uint64>(i)); + } else if (sizeof(i) == 32 / 8) { + return SimpleItoa(static_cast<uint32>(i)); + } +} + +// Required buffer size for FloatToBuffer is kFastToBufferSize. +char* FloatToBuffer(float i, char* buffer); + +} // namespace strings +} // namespace dynamic_depth + +#endif // DYNAMIC_DEPTH_INTERNAL_STRINGS_NUMBERS_H_ // NOLINT diff --git a/internal/strings/util.h b/internal/strings/util.h new file mode 100644 index 0000000..4252dc4 --- /dev/null +++ b/internal/strings/util.h @@ -0,0 +1,49 @@ +// Useful string functions and so forth. This is a grab-bag file. +// +// You might also want to look at memutil.h, which holds mem*() +// equivalents of a lot of the str*() functions in string.h, +// eg memstr, mempbrk, etc. +// +// These functions work fine for UTF-8 strings as long as you can +// consider them to be just byte strings. For example, due to the +// design of UTF-8 you do not need to worry about accidental matches, +// as long as all your inputs are valid UTF-8 (use \uHHHH, not \xHH or \oOOO). +// +// Caveats: +// * all the lengths in these routines refer to byte counts, +// not character counts. +// * case-insensitivity in these routines assumes that all the letters +// in question are in the range A-Z or a-z. +// +// If you need Unicode specific processing (for example being aware of +// Unicode character boundaries, or knowledge of Unicode casing rules, +// or various forms of equivalence and normalization), take a look at +// files in i18n/utf8. + +#ifndef DYNAMIC_DEPTH_INTERNAL_STRINGS_UTIL_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_STRINGS_UTIL_H_ // NOLINT + +#include <string> + +#include "base/port.h" +#include "fastmem.h" + +namespace dynamic_depth { + +// Returns whether str begins with prefix. +inline bool HasPrefixString(const string& str, const string& prefix) { + return str.length() >= prefix.length() && + ::dynamic_depth::strings::memeq(&str[0], &prefix[0], prefix.length()); +} + +// Returns whether str ends with suffix. +inline bool HasSuffixString(const string& str, const string& suffix) { + return str.length() >= suffix.length() && + ::dynamic_depth::strings::memeq( + &str[0] + (str.length() - suffix.length()), &suffix[0], + suffix.length()); +} + +} // namespace dynamic_depth + +#endif // DYNAMIC_DEPTH_INTERNAL_STRINGS_UTIL_H_ // NOLINT diff --git a/internal/xmpmeta/base64.cc b/internal/xmpmeta/base64.cc new file mode 100644 index 0000000..a21d8b5 --- /dev/null +++ b/internal/xmpmeta/base64.cc @@ -0,0 +1,85 @@ +#include "xmpmeta/base64.h" + +#include "android-base/logging.h" +#include "strings/escaping.h" + +namespace photos_editing_formats { +namespace { + +bool EncodeBase64RawData(const uint8* data, size_t data_size, string* output) { + // Disable linting because string_view doesn't appear to support uint8_t. + dynamic_depth::strings::Base64Escape(data, data_size, output, + false); // NOLINT + return output->length() > 0; +} + +template <typename T> +bool InternalEncodeArrayBase64(const std::vector<T>& data, string* output) { + size_t buffer_size = data.size() * sizeof(T); + return EncodeBase64RawData(reinterpret_cast<const uint8_t*>(data.data()), + buffer_size, output); +} + +template <typename T> +bool InternalDecodeArrayBase64(const string& data, std::vector<T>* output) { + string bytes; + if (!DecodeBase64(data, &bytes)) { + return false; + } + + const int count = bytes.size() / sizeof(T); + output->clear(); + output->resize(count); + memcpy(output->data(), bytes.data(), output->size() * sizeof(T)); + return !output->empty(); +} + +} // namespace + +// Decodes the base64-encoded input range. +bool DecodeBase64(const string& data, string* output) { + // Support decoding of both web-safe and regular base64. + // "Web-safe" base-64 replaces + with - and / with _, and omits + // trailing = padding characters. + if (dynamic_depth::absl::Base64Unescape(data, output)) { + return true; + } + return dynamic_depth::absl::WebSafeBase64Unescape(data, output); +} + +// Base64-encodes the given data. +bool EncodeBase64(const string& data, string* output) { + return EncodeBase64RawData(reinterpret_cast<const uint8*>(data.c_str()), + data.length(), output); +} + +// Base64-encodes the given int array. +bool EncodeIntArrayBase64(const std::vector<int32_t>& data, string* output) { + return InternalEncodeArrayBase64<int32_t>(data, output); +} + +// Base64-decodes the given base64-encoded string. +bool DecodeIntArrayBase64(const string& data, std::vector<int32_t>* output) { + return InternalDecodeArrayBase64<int32_t>(data, output); +} + +// Base64-encodes the given float array. +bool EncodeFloatArrayBase64(const std::vector<float>& data, string* output) { + return InternalEncodeArrayBase64<float>(data, output); +} + +// Base64-decodes the given base64-encoded string. +bool DecodeFloatArrayBase64(const string& data, std::vector<float>* output) { + return InternalDecodeArrayBase64<float>(data, output); +} + +// Base64-encodes the given double array. +bool EncodeDoubleArrayBase64(const std::vector<double>& data, string* output) { + return InternalEncodeArrayBase64<double>(data, output); +} + +// Base64-decodes the given base64-encoded string. +bool DecodeDoubleArrayBase64(const string& data, std::vector<double>* output) { + return InternalDecodeArrayBase64<double>(data, output); +} +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/base64.h b/internal/xmpmeta/base64.h new file mode 100644 index 0000000..8439e90 --- /dev/null +++ b/internal/xmpmeta/base64.h @@ -0,0 +1,39 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_XMPMETA_BASE64_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_XMPMETA_BASE64_H_ // NOLINT + +#include <iostream> +#include <string> +#include <vector> + +#include "base/port.h" + +namespace photos_editing_formats { +// Decodes the base64-encoded input range. Supports decoding of both web-safe +// and regular base64."Web-safe" base-64 replaces + with - and / with _, and +// omits trailing = padding characters. +bool DecodeBase64(const string& data, string* output); + +// Base64-encodes the given string. +bool EncodeBase64(const string& data, string* output); + +// Base64-encodes the given int array. +bool EncodeIntArrayBase64(const std::vector<int32_t>& data, string* output); + +// Base64-decodes the given int array. +bool DecodeIntArrayBase64(const string& data, std::vector<int32_t>* output); + +// Base64-encodes the given float array. +bool EncodeFloatArrayBase64(const std::vector<float>& data, string* output); + +// Base64-decodes the given float array. +bool DecodeFloatArrayBase64(const string& data, std::vector<float>* output); + +// Base64-encodes the given double array. +bool EncodeDoubleArrayBase64(const std::vector<double>& data, string* output); + +// Base64-decodes the given double array. +bool DecodeDoubleArrayBase64(const string& data, std::vector<double>* output); + +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_XMPMETA_BASE64_H_ // NOLINT diff --git a/internal/xmpmeta/file.cc b/internal/xmpmeta/file.cc new file mode 100644 index 0000000..5559219 --- /dev/null +++ b/internal/xmpmeta/file.cc @@ -0,0 +1,59 @@ +#include "xmpmeta/file.h" + +#include <cstdio> +#include "android-base/logging.h" + +namespace photos_editing_formats { + +using std::string; + +void WriteStringToFileOrDie(const string& data, const string& filename) { + FILE* file_descriptor = fopen(filename.c_str(), "wb"); + if (!file_descriptor) { + LOG(FATAL) << "Couldn't write to file: " << filename; + } + fwrite(data.c_str(), 1, data.size(), file_descriptor); + fclose(file_descriptor); +} + +void ReadFileToStringOrDie(const string& filename, string* data) { + FILE* file_descriptor = fopen(filename.c_str(), "r"); + + if (!file_descriptor) { + LOG(FATAL) << "Couldn't read file: " << filename; + } + + // Resize the input buffer appropriately. + fseek(file_descriptor, 0L, SEEK_END); + int num_bytes = ftell(file_descriptor); + data->resize(num_bytes); + + // Read the data. + fseek(file_descriptor, 0L, SEEK_SET); + int num_read = + fread(&((*data)[0]), sizeof((*data)[0]), num_bytes, file_descriptor); + if (num_read != num_bytes) { + LOG(FATAL) << "Couldn't read all of " << filename + << "expected bytes: " << num_bytes * sizeof((*data)[0]) + << "actual bytes: " << num_read; + } + fclose(file_descriptor); +} + +string JoinPath(const string& dirname, const string& basename) { +#ifdef _WIN32 + static const char separator = '\\'; +#else + static const char separator = '/'; +#endif // _WIN32 + + if ((!basename.empty() && basename[0] == separator) || dirname.empty()) { + return basename; + } else if (dirname[dirname.size() - 1] == separator) { + return dirname + basename; + } else { + return dirname + string(&separator, 1) + basename; + } +} + +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/file.h b/internal/xmpmeta/file.h new file mode 100644 index 0000000..43410b8 --- /dev/null +++ b/internal/xmpmeta/file.h @@ -0,0 +1,18 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_XMPMETA_FILE_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_XMPMETA_FILE_H_ // NOLINT + +#include <string> + +namespace photos_editing_formats { + +void WriteStringToFileOrDie(const std::string &data, + const std::string &filename); +void ReadFileToStringOrDie(const std::string &filename, std::string *data); + +// Join two path components, adding a slash if necessary. If basename is an +// absolute path then JoinPath ignores dirname and simply returns basename. +std::string JoinPath(const std::string &dirname, const std::string &basename); + +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_XMPMETA_FILE_H_ // NOLINT diff --git a/internal/xmpmeta/jpeg_io.cc b/internal/xmpmeta/jpeg_io.cc new file mode 100644 index 0000000..20fc7a6 --- /dev/null +++ b/internal/xmpmeta/jpeg_io.cc @@ -0,0 +1,194 @@ +#include "xmpmeta/jpeg_io.h" + +#include <fstream> +#include <sstream> + +#include "android-base/logging.h" + +namespace photos_editing_formats { +namespace { + +// File markers. +// See: http://www.fileformat.info/format/jpeg/egff.htm or +// https://en.wikipedia.org/wiki/JPEG +const int kSoi = 0xd8; // Start of image marker. +const int kApp1 = 0xe1; // Start of EXIF section. +const int kSos = 0xda; // Start of scan. + +// Number of bytes used to store a section's length in a JPEG file. +const int kSectionLengthByteSize = 2; + +// Returns the number of bytes available to be read. Sets the seek position +// to the place it was in before calling this function. +size_t GetBytesAvailable(std::istream* input_stream) { + const std::streamoff pos = input_stream->tellg(); + if (pos == -1) { + return 0; + } + + input_stream->seekg(0, std::ios::end); + if (!input_stream->good()) { + return 0; + } + + const std::streamoff end = input_stream->tellg(); + if (end == -1) { + return 0; + } + input_stream->seekg(pos); + + if (end <= pos) { + return 0; + } + return end - pos; +} + +// Returns the first byte in the stream cast to an integer. +int ReadByteAsInt(std::istream* input_stream) { + unsigned char byte; + input_stream->read(reinterpret_cast<char*>(&byte), 1); + if (!input_stream->good()) { + // Return an invalid value - no byte can be read as -1. + return -1; + } + return static_cast<int>(byte); +} + +// Reads the length of a section from 2 bytes. +size_t Read2ByteLength(std::istream* input_stream, bool* error) { + const int length_high = ReadByteAsInt(input_stream); + const int length_low = ReadByteAsInt(input_stream); + if (length_high == -1 || length_low == -1) { + *error = true; + return 0; + } + *error = false; + return length_high << 8 | length_low; +} + +bool HasPrefixString(const string& to_check, const string& prefix) { + if (to_check.size() < prefix.size()) { + return false; + } + return std::equal(prefix.begin(), prefix.end(), to_check.begin()); +} + +} // namespace + +Section::Section(const string& buffer) { + marker = kApp1; + is_image_section = false; + data = buffer; +} + +bool Section::IsMarkerApp1() { return marker == kApp1; } + +std::vector<Section> Parse(const ParseOptions& options, + std::istream* input_stream) { + std::vector<Section> sections; + // Return early if this is not the start of a JPEG section. + if (ReadByteAsInt(input_stream) != 0xff || + ReadByteAsInt(input_stream) != kSoi) { + LOG(WARNING) << "File's first two bytes does not match the sequence \xff" + << kSoi; + return std::vector<Section>(); + } + + int chr; // Short for character. + while ((chr = ReadByteAsInt(input_stream)) != -1) { + if (chr != 0xff) { + LOG(WARNING) << "Read non-padding byte: " << chr; + return sections; + } + // Skip padding bytes. + while ((chr = ReadByteAsInt(input_stream)) == 0xff) { + } + if (chr == -1) { + LOG(WARNING) << "No more bytes in file available to be read."; + return sections; + } + + const int marker = chr; + if (marker == kSos) { + // kSos indicates the image data will follow and no metadata after that, + // so read all data at one time. + if (!options.read_meta_only) { + Section section; + section.marker = marker; + section.is_image_section = true; + const size_t bytes_available = GetBytesAvailable(input_stream); + section.data.resize(bytes_available); + input_stream->read(§ion.data[0], bytes_available); + if (input_stream->good()) { + sections.push_back(section); + } + } + // All sections have been read. + return sections; + } + + bool error; + const size_t length = Read2ByteLength(input_stream, &error); + if (error || length < kSectionLengthByteSize) { + // No sections to read. + LOG(WARNING) << "No sections to read; section length is " << length; + return sections; + } + + const size_t bytes_left = GetBytesAvailable(input_stream); + if (length - kSectionLengthByteSize > bytes_left) { + LOG(WARNING) << "Invalid section length = " << length + << " total bytes available = " << bytes_left; + return sections; + } + + if (!options.read_meta_only || marker == kApp1) { + Section section; + section.marker = marker; + section.is_image_section = false; + const size_t data_size = length - kSectionLengthByteSize; + section.data.resize(data_size); + if (section.data.size() != data_size) { + LOG(WARNING) << "Discrepancy in section data size " + << section.data.size() << "and data size " << data_size; + return sections; + } + input_stream->read(§ion.data[0], section.data.size()); + if (input_stream->good() && + (options.section_header.empty() || + HasPrefixString(section.data, options.section_header))) { + sections.push_back(section); + // Return if we have specified to return the 1st section with + // the given name. + if (options.section_header_return_first) { + return sections; + } + } + } else { + // Skip this section since all EXIF/XMP meta will be in kApp1 section. + input_stream->ignore(length - kSectionLengthByteSize); + } + } + return sections; +} + +void WriteSections(const std::vector<Section>& sections, + std::ostream* output_stream) { + output_stream->put(0xff); + output_stream->put(kSoi); + for (const Section& section : sections) { + output_stream->put(0xff); + output_stream->put(section.marker); + if (!section.is_image_section) { + const int section_length = static_cast<int>(section.data.length()) + 2; + // It's not the image data. + const int lh = section_length >> 8; + const int ll = section_length & 0xff; + output_stream->put(lh); + output_stream->put(ll); + } + output_stream->write(section.data.c_str(), section.data.length()); + } +} + +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/md5.cc b/internal/xmpmeta/md5.cc new file mode 100644 index 0000000..1349483 --- /dev/null +++ b/internal/xmpmeta/md5.cc @@ -0,0 +1,223 @@ +#include "xmpmeta/md5.h" + +#include <string.h> // for memcpy(). + +#include <vector> + +#include "base/integral_types.h" +#include "strings/escaping.h" + +namespace photos_editing_formats { +namespace { + +const int kMd5DigestSize = 16; + +typedef struct MD5Context MD5_CTX; + +struct MD5Context { + uint32 buf[4]; + uint32 bits[2]; + uint32 in[16]; +}; + +void MD5Init(struct MD5Context* context); +void MD5Update(struct MD5Context* context, const uint8* data, size_t len); +void MD5Final(unsigned char digest[16], struct MD5Context* ctx); +void MD5Transform(uint32 buf[4], const uint32 in[16]); + +// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious +// initialization constants. +void MD5Init(MD5Context* context) { + context->buf[0] = 0x67452301; + context->buf[1] = 0xefcdab89; + context->buf[2] = 0x98badcfe; + context->buf[3] = 0x10325476; + context->bits[0] = 0; + context->bits[1] = 0; +} + +// Update context to reflect the concatenation of another buffer full of bytes. +void MD5Update(MD5Context* context, const uint8* data, size_t len) { + // Update bitcount. + uint32 t = context->bits[0]; + if ((context->bits[0] = t + (static_cast<uint32>(len) << 3)) < t) { + context->bits[1]++; // Carry from low to high. + } + context->bits[1] += len >> 29; + t = (t >> 3) & 0x3f; // Bytes already in shsInfo->data. + + // Handle any leading odd-sized chunks. + if (t) { + uint8* p = reinterpret_cast<uint8*>(context->in) + t; + + t = 64 - t; + if (len < t) { + memcpy(p, data, len); + return; + } + memcpy(p, data, t); + MD5Transform(context->buf, context->in); + data += t; + len -= t; + } + + // Process data in 64-byte chunks. + while (len >= 64) { + memcpy(context->in, data, 64); + MD5Transform(context->buf, context->in); + data += 64; + len -= 64; + } + + // Handle any remaining bytes of data. + memcpy(context->in, data, len); +} + +// Final wrapup - pad to 64-byte boundary with the bit pattern. +// 1 0* (64-bit count of bits processed, MSB-first) +void MD5Final(uint8 digest[16], MD5Context* ctx) { + // Compute number of bytes mod 64. + uint32 count = (ctx->bits[0] >> 3) & 0x3F; + + // Set the first char of padding to 0x80. This is safe since there is + // always at least one byte free. + uint8* p = reinterpret_cast<uint8*>(ctx->in) + count; + *p++ = 0x80; + + // Bytes of padding needed to make 64 bytes. + count = 64 - 1 - count; + + // Pad out to 56 mod 64. + if (count < 8) { + // Two lots of padding: Pad the first block to 64 bytes. + memset(p, 0, count); + MD5Transform(ctx->buf, ctx->in); + + // Now fill the next block with 56 bytes. + memset(ctx->in, 0, 56); + } else { + // Pad block to 56 bytes. + memset(p, 0, count - 8); + } + + // Append length in bits and transform. + ctx->in[14] = ctx->bits[0]; + ctx->in[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, ctx->in); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); // In case it's sensitive. +} + +// The four core functions - F1 is optimized somewhat. +// #define F1(x, y, z) (x & y | ~x & z) +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +// This is the central step in the MD5 algorithm. +#define MD5STEP(f, w, x, y, z, data, s) \ + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +// The core of the MD5 algorithm, this alters an existing MD5 hash to +// reflect the addition of 16 longwords of new data. MD5Update blocks +// the data and converts bytes into longwords for this routine. +void MD5Transform(uint32 buf[4], const uint32 in[16]) { + uint32 a = buf[0]; + uint32 b = buf[1]; + uint32 c = buf[2]; + uint32 d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +void MD5(const uint8_t* to_hash, size_t to_hash_length, uint8_t* output) { + MD5Context md5_context; + MD5Init(&md5_context); + MD5Update(&md5_context, to_hash, to_hash_length); + MD5Final(output, &md5_context); +} + +} // namespace + +string MD5Hash(const string& to_hash) { + std::vector<uint8_t> buffer; + buffer.resize(kMd5DigestSize); + MD5(reinterpret_cast<const uint8_t*>(to_hash.data()), to_hash.length(), + &buffer[0]); + return dynamic_depth::strings::b2a_hex( + reinterpret_cast<const char*>(&buffer[0]), kMd5DigestSize); +} + +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/xml/const.cc b/internal/xmpmeta/xml/const.cc new file mode 100644 index 0000000..8f0eb5c --- /dev/null +++ b/internal/xmpmeta/xml/const.cc @@ -0,0 +1,33 @@ +#include "xmpmeta/xml/const.h" + +namespace photos_editing_formats { +namespace xml { + +const char* XmlConst::EncodingStr() { return "UTF-8"; } + +// RDF metadata constants. +const char* XmlConst::RdfAbout() { return "about"; } + +const char* XmlConst::RdfDescription() { return "Description"; } + +const char* XmlConst::RdfNodeName() { return "RDF"; } + +const char* XmlConst::RdfNodeNs() { + return "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; +} + +const char* XmlConst::RdfPrefix() { return "rdf"; } + +const char* XmlConst::RdfSeq() { return "Seq"; } + +const char* XmlConst::RdfLi() { return "li"; } + +// XML metadata constants. +const char* XmlConst::NsAttrName() { return "xmlns"; } + +const char* XmlConst::Separator() { return ":"; } + +const char* XmlConst::Version() { return "1.0"; } + +} // namespace xml +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/xml/const.h b/internal/xmpmeta/xml/const.h new file mode 100644 index 0000000..8a8a056 --- /dev/null +++ b/internal/xmpmeta/xml/const.h @@ -0,0 +1,29 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_CONST_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_CONST_H_ // NOLINT + +namespace photos_editing_formats { +namespace xml { + +struct XmlConst { + // Encoding type. + static const char* EncodingStr(); + + // RDF metadata. + static const char* RdfAbout(); + static const char* RdfDescription(); + static const char* RdfNodeName(); + static const char* RdfNodeNs(); + static const char* RdfPrefix(); + static const char* RdfSeq(); + static const char* RdfLi(); + + // XML metadata. + static const char* NsAttrName(); + static const char* Separator(); + static const char* Version(); +}; + +} // namespace xml +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_CONST_H_ // NOLINT diff --git a/internal/xmpmeta/xml/deserializer.h b/internal/xmpmeta/xml/deserializer.h new file mode 100644 index 0000000..e717950 --- /dev/null +++ b/internal/xmpmeta/xml/deserializer.h @@ -0,0 +1,65 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_DESERIALIZER_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_DESERIALIZER_H_ // NOLINT + +#include <memory> +#include <string> +#include <vector> + +#include "base/integral_types.h" +#include "base/port.h" + +namespace photos_editing_formats { +namespace xml { + +// Performs deserialization. +// Example: +// Deserializer deserializer(); +// string revision; +// deserializer.ParseString("Revision", &revision); +class Deserializer { + public: + virtual ~Deserializer() {} + + // Returns a Deserializer. + // child_name is the name of the next node to deserialize. + virtual std::unique_ptr<Deserializer> CreateDeserializer( + const string& prefix, const string& child_name) const = 0; + + // Returns a Deserializer from a list element node. + virtual std::unique_ptr<Deserializer> CreateDeserializerFromListElementAt( + const string& prefix, const string& list_name, int index) const = 0; + + // Parsers for properties with the given prefix. + // Parses a node such as <NodeName Prefix:Name="Value" /> + virtual bool ParseBase64(const string& prefix, const string& name, + string* value) const = 0; + virtual bool ParseIntArrayBase64(const string& prefix, const string& name, + std::vector<int>* values) const = 0; + virtual bool ParseFloatArrayBase64(const string& prefix, const string& name, + std::vector<float>* values) const = 0; + virtual bool ParseDoubleArrayBase64(const string& prefix, const string& name, + std::vector<double>* values) const = 0; + virtual bool ParseBoolean(const string& prefix, const string& name, + bool* value) const = 0; + virtual bool ParseInt(const string& prefix, const string& name, + int* value) const = 0; + virtual bool ParseFloat(const string& prefix, const string& name, + float* value) const = 0; + virtual bool ParseDouble(const string& prefix, const string& name, + double* value) const = 0; + virtual bool ParseLong(const string& prefix, const string& name, + int64* value) const = 0; + virtual bool ParseString(const string& prefix, const string& name, + string* value) const = 0; + + // Parsers for arrays. + virtual bool ParseIntArray(const string& prefix, const string& list_name, + std::vector<int>* values) const = 0; + virtual bool ParseDoubleArray(const string& prefix, const string& list_name, + std::vector<double>* values) const = 0; +}; + +} // namespace xml +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_DESERIALIZER_H_ // NOLINT diff --git a/internal/xmpmeta/xml/deserializer_impl.cc b/internal/xmpmeta/xml/deserializer_impl.cc new file mode 100644 index 0000000..6214a0c --- /dev/null +++ b/internal/xmpmeta/xml/deserializer_impl.cc @@ -0,0 +1,321 @@ +#include "xmpmeta/xml/deserializer_impl.h" + +#include <algorithm> + +#include "base/integral_types.h" +#include "android-base/logging.h" +#include "strings/numbers.h" +#include "xmpmeta/base64.h" +#include "xmpmeta/xml/const.h" +#include "xmpmeta/xml/search.h" +#include "xmpmeta/xml/utils.h" +#include "xmpmeta/xmp_parser.h" + +namespace photos_editing_formats { +namespace xml { +namespace { + +// Converts a string to a boolean value if bool_str is one of "false" or "true", +// regardless of letter casing. +bool BoolStringToBool(const string& bool_str, bool* value) { + string bool_str_lower = bool_str; + std::transform(bool_str_lower.begin(), bool_str_lower.end(), + bool_str_lower.begin(), ::tolower); + if (bool_str_lower == "true") { + *value = true; + return true; + } + if (bool_str_lower == "false") { + *value = false; + return true; + } + return false; +} + +// Search for an rdf:Seq node, if it hasn't already been set. +// parent_name is the name of the rdf:Seq node's parent. +xmlNodePtr FindSeqNode(const xmlNodePtr node, const string& prefix, + const string& parent_name) { + xmlNodePtr parent_node = + DepthFirstSearch(node, prefix.data(), parent_name.data()); + if (parent_node == nullptr) { + LOG(WARNING) << "Node " << parent_name << " not found"; + return nullptr; + } + return GetFirstSeqElement(parent_node); +} + +// Extracts the specified string attribute. +bool GetStringProperty(const xmlNodePtr node, const string& prefix, + const string& property, string* value) { + const xmlDocPtr doc = node->doc; + for (const _xmlAttr* attribute = node->properties; attribute != nullptr; + attribute = attribute->next) { + // If prefix is not empty, then the attribute's namespace must not be null. + if (((attribute->ns && !prefix.empty() && + strcmp(FromXmlChar(attribute->ns->prefix), prefix.data()) == 0) || + prefix.empty()) && + strcmp(FromXmlChar(attribute->name), property.data()) == 0) { + xmlChar* attribute_string = + xmlNodeListGetString(doc, attribute->children, 1); + *value = FromXmlChar(attribute_string); + xmlFree(attribute_string); + return true; + } + } + LOG(WARNING) << "Could not find string attribute: " << property; + return false; +} + +// Reads the contents of a node. +// E.g. <prefix:node_name>Contents Here</prefix:node_name> +bool ReadNodeContent(const xmlNodePtr node, const string& prefix, + const string& node_name, string* value) { + auto* element = DepthFirstSearch(node, prefix.data(), node_name.data()); + if (element == nullptr) { + return false; + } + if (!prefix.empty() && + (element->ns == nullptr || element->ns->prefix == nullptr || + strcmp(FromXmlChar(element->ns->prefix), prefix.data()) != 0)) { + return false; + } + xmlChar* node_content = xmlNodeGetContent(element); + *value = FromXmlChar(node_content); + free(node_content); + return true; +} + +// Reads the string value of a property from the given XML node. +bool ReadStringProperty(const xmlNodePtr node, const string& prefix, + const string& property, string* value) { + if (node == nullptr) { + return false; + } + if (property.empty()) { + LOG(ERROR) << "Property not given"; + return false; + } + + // Try parsing in the format <Node ... Prefix:Property="Value"/> + bool success = GetStringProperty(node, prefix, property, value); + if (!success) { + // Try parsing in the format <Prefix:Property>Value</Prefix:Property> + success = ReadNodeContent(node, prefix, property, value); + } + return success; +} + +// Same as ReadStringProperty, but applies base-64 decoding to the output. +bool ReadBase64Property(const xmlNodePtr node, const string& prefix, + const string& property, string* value) { + string base64_data; + if (!ReadStringProperty(node, prefix, property, &base64_data)) { + return false; + } + return DecodeBase64(base64_data, value); +} + +} // namespace + +DeserializerImpl::DeserializerImpl(const xmlNodePtr node) + : node_(node), list_node_(nullptr) {} + +// Public methods. +std::unique_ptr<Deserializer> DeserializerImpl::CreateDeserializer( + const string& prefix, const string& child_name) const { + if (child_name.empty()) { + LOG(ERROR) << "Child name is empty"; + return nullptr; + } + xmlNodePtr child_node = + DepthFirstSearch(node_, prefix.data(), child_name.data()); + if (child_node == nullptr) { + return nullptr; + } + return std::unique_ptr<Deserializer>( + new DeserializerImpl(child_node)); // NOLINT +} + +std::unique_ptr<Deserializer> +DeserializerImpl::CreateDeserializerFromListElementAt(const string& prefix, + const string& list_name, + int index) const { + if (index < 0) { + LOG(ERROR) << "Index must be greater than or equal to zero"; + return nullptr; + } + + if (list_name.empty()) { + LOG(ERROR) << "Parent name cannot be empty"; + return nullptr; + } + // Search for an rdf:Seq node, if the name of list_node_ doesn't match + // the given parent name. + // Ensures thread safety. + const xmlNodePtr list_node = [&] { + std::lock_guard<std::mutex> lock(mtx_); + if (list_node_ == nullptr || + string(FromXmlChar(list_node_->name)) != list_name) { + list_node_ = DepthFirstSearch(node_, prefix.data(), list_name.data()); + } + return list_node_; + }(); + if (list_node == nullptr) { + return nullptr; + } + + xmlNodePtr seq_node = GetFirstSeqElement(list_node); + if (seq_node == nullptr) { + LOG(ERROR) << "No rdf:Seq node found on " << list_name; + return nullptr; + } + xmlNodePtr li_node = GetElementAt(seq_node, index); + if (li_node == nullptr) { + return nullptr; + } + // Return a new Deserializer with the current rdf:li node and the current + // node name. + return std::unique_ptr<Deserializer>( + new DeserializerImpl(li_node)); // NOLINT +} + +bool DeserializerImpl::ParseBase64(const string& prefix, const string& name, + string* value) const { + return ReadBase64Property(node_, prefix, name, value); +} + +bool DeserializerImpl::ParseIntArrayBase64(const string& prefix, + const string& name, + std::vector<int>* values) const { + string base64_data; + if (!ReadStringProperty(node_, prefix, name, &base64_data)) { + return false; + } + return DecodeIntArrayBase64(base64_data, values); +} + +bool DeserializerImpl::ParseFloatArrayBase64(const string& prefix, + const string& name, + std::vector<float>* values) const { + string base64_data; + if (!ReadStringProperty(node_, prefix, name, &base64_data)) { + return false; + } + return DecodeFloatArrayBase64(base64_data, values); +} + +bool DeserializerImpl::ParseDoubleArrayBase64( + const string& prefix, const string& name, + std::vector<double>* values) const { + string base64_data; + if (!ReadStringProperty(node_, prefix, name, &base64_data)) { + return false; + } + return DecodeDoubleArrayBase64(base64_data, values); +} + +bool DeserializerImpl::ParseBoolean(const string& prefix, const string& name, + bool* value) const { + string string_value; + if (!ReadStringProperty(node_, prefix, name, &string_value)) { + return false; + } + return BoolStringToBool(string_value, value); +} + +bool DeserializerImpl::ParseDouble(const string& prefix, const string& name, + double* value) const { + string string_value; + if (!ReadStringProperty(node_, prefix, name, &string_value)) { + return false; + } + *value = std::stod(string_value); + return true; +} + +bool DeserializerImpl::ParseInt(const string& prefix, const string& name, + int* value) const { + string string_value; + if (!ReadStringProperty(node_, prefix, name, &string_value)) { + return false; + } + *value = std::stoi(string_value); // NOLINT + return true; +} + +bool DeserializerImpl::ParseFloat(const string& prefix, const string& name, + float* value) const { + string string_value; + if (!ReadStringProperty(node_, prefix, name, &string_value)) { + return false; + } + *value = std::stof(string_value); + return true; +} + +bool DeserializerImpl::ParseLong(const string& prefix, const string& name, + int64* value) const { + string string_value; + if (!ReadStringProperty(node_, prefix, name, &string_value)) { + return false; + } + *value = std::stol(string_value); + return true; +} + +bool DeserializerImpl::ParseString(const string& prefix, const string& name, + string* value) const { + return ReadStringProperty(node_, prefix, name, value); +} + +bool DeserializerImpl::ParseIntArray(const string& prefix, + const string& list_name, + std::vector<int>* values) const { + xmlNodePtr seq_node = FindSeqNode(node_, prefix, list_name); + if (seq_node == nullptr) { + return false; + } + values->clear(); + int i = 0; + for (xmlNodePtr li_node = GetElementAt(seq_node, 0); li_node != nullptr; + li_node = GetElementAt(seq_node, ++i)) { + string value = GetLiNodeContent(li_node); + for (int i = 0; i < value.size(); ++i) { + if (!isdigit(value[i])) { + LOG(ERROR) << "Could not parse rdf:li node value to an integer"; + return false; + } + } + int int_value = std::atoi(value.c_str()); // NOLINT + values->push_back(int_value); + } + + return true; +} + +bool DeserializerImpl::ParseDoubleArray(const string& prefix, + const string& list_name, + std::vector<double>* values) const { + xmlNodePtr seq_node = FindSeqNode(node_, prefix, list_name); + if (seq_node == nullptr) { + return false; + } + values->clear(); + int i = 0; + for (xmlNodePtr li_node = GetElementAt(seq_node, 0); li_node != nullptr; + li_node = GetElementAt(seq_node, ++i)) { + double double_value; + if (!dynamic_depth::strings::safe_strtod(GetLiNodeContent(li_node), + &double_value)) { + LOG(ERROR) << "Could not parse rdf:li node value to a double"; + return false; + } + values->push_back(double_value); + } + + return true; +} + +} // namespace xml +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/xml/deserializer_impl.h b/internal/xmpmeta/xml/deserializer_impl.h new file mode 100644 index 0000000..65df2d6 --- /dev/null +++ b/internal/xmpmeta/xml/deserializer_impl.h @@ -0,0 +1,93 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_DESERIALIZER_IMPL_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_DESERIALIZER_IMPL_H_ // NOLINT + +#include <libxml/tree.h> + +#include <map> +#include <mutex> // NOLINT(build/c++11) +#include <string> + +#include "base/port.h" +#include "xmpmeta/xml/deserializer.h" + +namespace photos_editing_formats { +namespace xml { + +// Deserializes an XML node. +// Example: +// xmlNodePtr device_node = +// DepthFirstSearch(xmp.ExtendedSection(), "Device", "Description"); +// DeserializerImpl deserializer(device_node); +// string revision; +// deserializer.ParseString("Device", "Revision", &revision); +// TODO(miraleung): Add example for list node deserializer. +class DeserializerImpl : public Deserializer { + public: + // Creates a deserializer with a null rdf:Seq node. + explicit DeserializerImpl(const xmlNodePtr node); + + // Returns a Deserializer. + // If prefix is empty, the deserializer will be created on the first node + // found with a name that matches child_name. + // child_name is the name of the next node to deserialize. + std::unique_ptr<Deserializer> CreateDeserializer( + const string& prefix, const string& child_name) const override; + + // Returns a Deserializer from a list element node, if one is available as + // a descendant of node_. + // If prefix is empty, the deserializer will be created on the first node + // found with a name that matches child_name. + // Returns null if seq_node_ is null or if the index is out of range. + std::unique_ptr<Deserializer> CreateDeserializerFromListElementAt( + const string& prefix, const string& list_name, int index) const override; + + // Parsers for XML properties. + // If prefix is empty, the node's namespace may be null. Otherwise, it must + // not be null. + bool ParseBase64(const string& prefix, const string& name, + string* value) const override; + bool ParseIntArrayBase64(const string& prefix, const string& name, + std::vector<int>* values) const override; + bool ParseFloatArrayBase64(const string& prefix, const string& name, + std::vector<float>* values) const override; + bool ParseDoubleArrayBase64(const string& prefix, const string& name, + std::vector<double>* values) const override; + bool ParseBoolean(const string& prefix, const string& name, + bool* value) const override; + bool ParseDouble(const string& prefix, const string& name, + double* value) const override; + bool ParseInt(const string& prefix, const string& name, + int* value) const override; + bool ParseFloat(const string& prefix, const string& name, + float* value) const override; + bool ParseLong(const string& prefix, const string& name, + int64* value) const override; + bool ParseString(const string& prefix, const string& name, + string* value) const override; + + // Parses the numbers in an rdf:Seq list into the values collection. + // The given collection is cleared of any existing values, and the + // parsed numbers are written to it. + bool ParseIntArray(const string& prefix, const string& list_name, + std::vector<int>* values) const override; + bool ParseDoubleArray(const string& prefix, const string& list_name, + std::vector<double>* values) const override; + + // Disallow copying. + DeserializerImpl(const DeserializerImpl&) = delete; + void operator=(const DeserializerImpl&) = delete; + + private: + xmlNodePtr node_; + // Remembers the parent node of the last deserializer created on the rdf:Seq + // node. For performance reasons only, to avoid unnessarily traversing + // the XML document tree. + mutable xmlNodePtr list_node_; + // Lock modifications of list_node_ in const functions to make it thread-safe. + mutable std::mutex mtx_; +}; + +} // namespace xml +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_DESERIALIZER_IMPL_H_ // NOLINT diff --git a/internal/xmpmeta/xml/search.cc b/internal/xmpmeta/xml/search.cc new file mode 100644 index 0000000..ca2e45c --- /dev/null +++ b/internal/xmpmeta/xml/search.cc @@ -0,0 +1,73 @@ +#include "xmpmeta/xml/search.h" + +#include <stack> +#include <string> + +#include "android-base/logging.h" +#include "xmpmeta/xml/utils.h" + +using photos_editing_formats::xml::FromXmlChar; + +namespace photos_editing_formats { +namespace xml { + +xmlNodePtr DepthFirstSearch(const xmlDocPtr parent, const char* name) { + return DepthFirstSearch(parent, "", name); +} + +xmlNodePtr DepthFirstSearch(const xmlDocPtr parent, const char* prefix, + const char* name) { + if (parent == nullptr || parent->children == nullptr) { + LOG(ERROR) << "XML doc was null or has no XML nodes"; + return nullptr; + } + xmlNodePtr result; + for (xmlNodePtr node = parent->children; node != nullptr; node = node->next) { + result = DepthFirstSearch(node, prefix, name); + if (result != nullptr) { + return result; + } + } + LOG(WARNING) << "No node matching " << prefix << ":" << name << " was found"; + return nullptr; +} + +xmlNodePtr DepthFirstSearch(const xmlNodePtr parent, const char* name) { + return DepthFirstSearch(parent, "", name); +} + +xmlNodePtr DepthFirstSearch(const xmlNodePtr parent, const char* prefix, + const char* name) { + if (parent == nullptr) { + LOG(ERROR) << "XML node was null"; + return nullptr; + } + std::stack<xmlNodePtr> node_stack; + node_stack.push(parent); + while (!node_stack.empty()) { + const xmlNodePtr current_node = node_stack.top(); + node_stack.pop(); + if (strcmp(FromXmlChar(current_node->name), name) == 0) { + if (!prefix || strlen(prefix) == 0) { + return current_node; + } + if (current_node->ns && current_node->ns->prefix && + strcmp(FromXmlChar(current_node->ns->prefix), prefix) == 0) { + return current_node; + } + } + std::stack<xmlNodePtr> stack_to_reverse; + for (xmlNodePtr child = current_node->children; child != nullptr; + child = child->next) { + stack_to_reverse.push(child); + } + while (!stack_to_reverse.empty()) { + node_stack.push(stack_to_reverse.top()); + stack_to_reverse.pop(); + } + } + return nullptr; +} + +} // namespace xml +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/xml/search.h b/internal/xmpmeta/xml/search.h new file mode 100644 index 0000000..c420661 --- /dev/null +++ b/internal/xmpmeta/xml/search.h @@ -0,0 +1,36 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_SEARCH_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_SEARCH_H_ // NOLINT + +#include <libxml/tree.h> + +// Performs searches an XML tree. +namespace photos_editing_formats { +namespace xml { + +// Depth-first search on the nodes in this XML doc. +// Performs Depth first search on the child XML elements in order. +// Returns the first child element with a matching node name. If not found, +// returns a null pointer. +xmlNodePtr DepthFirstSearch(const xmlDocPtr parent, const char* name); + +// Returns the first child element with a matching prefix and name. +// If prefix is null or empty, this has the same effect as the method abouve. +// Otherwise, the resulting node's namespace and its name must not be null. +xmlNodePtr DepthFirstSearch(const xmlDocPtr parent, const char* prefix, + const char* name); + +// Depth-first search on the parent, for a child element with the given name. +// The element name excludes its prefix. +// Returns a null pointer if no matching element is found. +xmlNodePtr DepthFirstSearch(const xmlNodePtr parent, const char* name); + +// Returns the first child element with a matching prefix and name. +// If prefix is null or empty, this has the same effect as the method abouve. +// Otherwise, the resulting node's namespace and its name must not be null. +xmlNodePtr DepthFirstSearch(const xmlNodePtr parent, const char* prefix, + const char* name); + +} // namespace xml +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_SEARCH_H_ // NOLINT diff --git a/internal/xmpmeta/xml/serializer.h b/internal/xmpmeta/xml/serializer.h new file mode 100644 index 0000000..9d35ad8 --- /dev/null +++ b/internal/xmpmeta/xml/serializer.h @@ -0,0 +1,76 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_SERIALIZER_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_SERIALIZER_H_ // NOLINT + +#include <memory> +#include <string> +#include <vector> + +#include "base/port.h" + +namespace photos_editing_formats { +namespace xml { + +// Serializes properties for a hierarchy of objects. +// Example: +// BookSerializer serializer(); +// // Serialize a list of objects. +// std::unique_ptr<Serializer> book_list_serializer = +// serializer->CreateListSerializer("Books"); +// for (Book *book : book_list) { +// std::unique_ptr<Serializer> book_serializer = +// cameras_serializer->CreateItemSerializer("Book"); +// success &= book->Serialize(book_serializer.get()); +// +// // Write properties in an object. +// // This would be called from the Book class. +// string book_name("Book"); +// std::unique_ptr<Serializer> book_info_serializer = +// book_serializer->CreateSerializer("Info"); +// book_info_serializer->WriteProperty("Author", "Cereal Eyser"); +// book_info_serializer->WriteProperty("ISBN", "314159265359"); +// std::unique_ptr<Serializer> genre_serializer = +// book_serializer->CreateSeralizer("Genre", true); +// std::unique_ptr<Serializer> fantasy_serializer = +// genre_serializer->CreateSerialzer("Fantasy"); +// // Serialize genre properties here. +// } + +class Serializer { + public: + virtual ~Serializer() {} + + // Returns a Serializer for an object that is an item in a list. + virtual std::unique_ptr<Serializer> CreateItemSerializer( + const string& prefix, const string& item_name) const = 0; + + // Returns a Serializer for a list of objects. + virtual std::unique_ptr<Serializer> CreateListSerializer( + const string& prefix, const string& list_name) const = 0; + + // Creates a serializer from the current serializer. + // node_ns_name is the XML namespace to which the newly created node belongs. + // If this parameter is an empty string, the new node will not belong to a + // namespace. + // node_name is the name of the new node. This parameter cannot be an empty + // string. + virtual std::unique_ptr<Serializer> CreateSerializer( + const string& node_ns_name, const string& node_name) const = 0; + + // Serializes a property with the given prefix. + // Example: <NodeName PropertyPrefix:PropertyName="PropertyValue" /> + virtual bool WriteBoolProperty(const string& prefix, const string& name, + bool value) const = 0; + virtual bool WriteProperty(const string& prefix, const string& name, + const string& value) const = 0; + + // Serializes the collection of values. + virtual bool WriteIntArray(const string& prefix, const string& array_name, + const std::vector<int>& values) const = 0; + virtual bool WriteDoubleArray(const string& prefix, const string& array_name, + const std::vector<double>& values) const = 0; +}; + +} // namespace xml +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_SERIALIZER_H_ // NOLINT diff --git a/internal/xmpmeta/xml/serializer_impl.cc b/internal/xmpmeta/xml/serializer_impl.cc new file mode 100644 index 0000000..c8a6038 --- /dev/null +++ b/internal/xmpmeta/xml/serializer_impl.cc @@ -0,0 +1,247 @@ +#include "xmpmeta/xml/serializer_impl.h" + +#include <libxml/tree.h> + +#include "base/integral_types.h" +#include "android-base/logging.h" +#include "strings/numbers.h" +#include "xmpmeta/xml/const.h" +#include "xmpmeta/xml/utils.h" + +namespace photos_editing_formats { +namespace xml { + +// Methods specific to SerializerImpl. +SerializerImpl::SerializerImpl( + const std::unordered_map<string, xmlNsPtr>& namespaces, xmlNodePtr node) + : node_(node), namespaces_(namespaces) { + CHECK(node_ != nullptr) << "Node cannot be null"; + CHECK(node_->name != nullptr) << "Name in the XML node cannot be null"; +} + +bool SerializerImpl::SerializeNamespaces() { + if (namespaces_.empty()) { + return true; + } + if (node_->ns == nullptr && !namespaces_.empty()) { + return false; + } + // Check that the namespaces all have hrefs and that there is a value + // for the key node_name. + // Set the namespaces in the root node. + xmlNsPtr node_ns = node_->ns; + for (const auto& entry : namespaces_) { + CHECK(entry.second->href != nullptr) << "Namespace href cannot be null"; + if (node_ns != nullptr) { + node_ns->next = entry.second; + } + node_ns = entry.second; + } + return true; +} + +std::unique_ptr<SerializerImpl> SerializerImpl::FromDataAndSerializeNamespaces( + const std::unordered_map<string, xmlNsPtr>& namespaces, xmlNodePtr node) { + std::unique_ptr<SerializerImpl> serializer = + std::unique_ptr<SerializerImpl>( // NOLINT + new SerializerImpl(namespaces, node)); // NOLINT + if (!serializer->SerializeNamespaces()) { + LOG(ERROR) << "Could not serialize namespaces"; + return nullptr; + } + return serializer; +} + +// Implemented methods. +std::unique_ptr<Serializer> SerializerImpl::CreateSerializer( + const string& node_ns_name, const string& node_name) const { + if (node_name.empty()) { + LOG(ERROR) << "Node name is empty"; + return nullptr; + } + + if (namespaces_.count(node_ns_name) == 0 && !node_ns_name.empty()) { + LOG(ERROR) << "Prefix " << node_ns_name << " not found in prefix list"; + return nullptr; + } + + xmlNodePtr new_node = + xmlNewNode(node_ns_name.empty() ? nullptr : namespaces_.at(node_ns_name), + ToXmlChar(node_name.data())); + xmlAddChild(node_, new_node); + return std::unique_ptr<Serializer>( + new SerializerImpl(namespaces_, new_node)); // NOLINT +} + +std::unique_ptr<Serializer> SerializerImpl::CreateItemSerializer( + const string& prefix, const string& item_name) const { + if (namespaces_.count(XmlConst::RdfPrefix()) == 0 || + namespaces_.at(XmlConst::RdfPrefix()) == nullptr) { + LOG(ERROR) << "No RDF prefix namespace found"; + return nullptr; + } + if (!prefix.empty() && !namespaces_.count(prefix)) { + LOG(ERROR) << "No namespace found for " << prefix; + return nullptr; + } + if (strcmp(XmlConst::RdfSeq(), FromXmlChar(node_->name)) != 0) { + LOG(ERROR) << "No rdf:Seq node for serializing this item"; + return nullptr; + } + + xmlNsPtr rdf_prefix_ns = namespaces_.at(string(XmlConst::RdfPrefix())); + xmlNodePtr li_node = xmlNewNode(nullptr, ToXmlChar(XmlConst::RdfLi())); + xmlNodePtr new_node = + xmlNewNode(prefix.empty() ? nullptr : namespaces_.at(prefix), + ToXmlChar(item_name.data())); + xmlSetNs(li_node, rdf_prefix_ns); + xmlAddChild(node_, li_node); + xmlAddChild(li_node, new_node); + return std::unique_ptr<Serializer>( + new SerializerImpl(namespaces_, new_node)); // NOLINT +} + +std::unique_ptr<Serializer> SerializerImpl::CreateListSerializer( + const string& prefix, const string& list_name) const { + if (namespaces_.count(XmlConst::RdfPrefix()) == 0 || + namespaces_.at(XmlConst::RdfPrefix()) == nullptr) { + LOG(ERROR) << "No RDF prefix namespace found"; + return nullptr; + } + if (!prefix.empty() && !namespaces_.count(prefix)) { + LOG(ERROR) << "No namespace found for " << prefix; + return nullptr; + } + + xmlNodePtr list_node = + xmlNewNode(prefix.empty() ? nullptr : namespaces_.at(prefix), + ToXmlChar(list_name.data())); + xmlNsPtr rdf_prefix_ns = namespaces_.at(string(XmlConst::RdfPrefix())); + xmlNodePtr seq_node = xmlNewNode(nullptr, ToXmlChar(XmlConst::RdfSeq())); + xmlSetNs(seq_node, rdf_prefix_ns); + xmlAddChild(list_node, seq_node); + xmlAddChild(node_, list_node); + return std::unique_ptr<Serializer>( + new SerializerImpl(namespaces_, seq_node)); // NOLINT +} + +bool SerializerImpl::WriteBoolProperty(const string& prefix, const string& name, + bool value) const { + const string& bool_str = (value ? "true" : "false"); + return WriteProperty(prefix, name, bool_str); +} + +bool SerializerImpl::WriteProperty(const string& prefix, const string& name, + const string& value) const { + if (!strcmp(XmlConst::RdfSeq(), FromXmlChar(node_->name))) { + LOG(ERROR) << "Cannot write a property on an rdf:Seq node"; + return false; + } + if (name.empty()) { + LOG(ERROR) << "Property name is empty"; + return false; + } + + // Check that prefix has a corresponding namespace href. + if (!prefix.empty() && namespaces_.count(prefix) == 0) { + LOG(ERROR) << "No namespace found for prefix " << prefix; + return false; + } + + // Serialize the property in the format Prefix:Name="Value". + xmlSetNsProp(node_, prefix.empty() ? nullptr : namespaces_.at(prefix), + ToXmlChar(name.data()), ToXmlChar(value.data())); + return true; +} + +bool SerializerImpl::WriteIntArray(const string& prefix, + const string& array_name, + const std::vector<int>& values) const { + if (!strcmp(XmlConst::RdfSeq(), FromXmlChar(node_->name))) { + LOG(ERROR) << "Cannot write a property on an rdf:Seq node"; + return false; + } + if (values.empty()) { + LOG(WARNING) << "No values to write"; + return false; + } + if (namespaces_.count(XmlConst::RdfPrefix()) == 0 || + namespaces_.at(XmlConst::RdfPrefix()) == nullptr) { + LOG(ERROR) << "No RDF prefix found"; + return false; + } + if (!prefix.empty() && !namespaces_.count(prefix)) { + LOG(ERROR) << "No namespace found for " << prefix; + return false; + } + if (array_name.empty()) { + LOG(ERROR) << "Parent name cannot be empty"; + return false; + } + + xmlNodePtr array_parent_node = + xmlNewNode(prefix.empty() ? nullptr : namespaces_.at(prefix), + ToXmlChar(array_name.data())); + xmlAddChild(node_, array_parent_node); + + xmlNsPtr rdf_prefix_ns = namespaces_.at(XmlConst::RdfPrefix()); + xmlNodePtr seq_node = xmlNewNode(nullptr, ToXmlChar(XmlConst::RdfSeq())); + xmlSetNs(seq_node, rdf_prefix_ns); + xmlAddChild(array_parent_node, seq_node); + for (int value : values) { + xmlNodePtr li_node = xmlNewNode(nullptr, ToXmlChar(XmlConst::RdfLi())); + xmlSetNs(li_node, rdf_prefix_ns); + xmlAddChild(seq_node, li_node); + xmlNodeSetContent(li_node, ToXmlChar(std::to_string(value).c_str())); + } + + return true; +} + +bool SerializerImpl::WriteDoubleArray(const string& prefix, + const string& array_name, + const std::vector<double>& values) const { + if (!strcmp(XmlConst::RdfSeq(), FromXmlChar(node_->name))) { + LOG(ERROR) << "Cannot write a property on an rdf:Seq node"; + return false; + } + if (values.empty()) { + LOG(WARNING) << "No values to write"; + return false; + } + if (namespaces_.count(XmlConst::RdfPrefix()) == 0 || + namespaces_.at(XmlConst::RdfPrefix()) == nullptr) { + LOG(ERROR) << "No RDF prefix found"; + return false; + } + if (!prefix.empty() && !namespaces_.count(prefix)) { + LOG(ERROR) << "No namespace found for " << prefix; + return false; + } + if (array_name.empty()) { + LOG(ERROR) << "Parent name cannot be empty"; + return false; + } + + xmlNodePtr array_parent_node = + xmlNewNode(prefix.empty() ? nullptr : namespaces_.at(prefix), + ToXmlChar(array_name.data())); + xmlAddChild(node_, array_parent_node); + + xmlNsPtr rdf_prefix_ns = namespaces_.at(XmlConst::RdfPrefix()); + xmlNodePtr seq_node = xmlNewNode(nullptr, ToXmlChar(XmlConst::RdfSeq())); + xmlSetNs(seq_node, rdf_prefix_ns); + xmlAddChild(array_parent_node, seq_node); + for (float value : values) { + xmlNodePtr li_node = xmlNewNode(nullptr, ToXmlChar(XmlConst::RdfLi())); + xmlSetNs(li_node, rdf_prefix_ns); + xmlAddChild(seq_node, li_node); + xmlNodeSetContent( + li_node, ToXmlChar(dynamic_depth::strings::SimpleFtoa(value).c_str())); + } + + return true; +} + +} // namespace xml +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/xml/serializer_impl.h b/internal/xmpmeta/xml/serializer_impl.h new file mode 100644 index 0000000..78131c5 --- /dev/null +++ b/internal/xmpmeta/xml/serializer_impl.h @@ -0,0 +1,177 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_SERIALIZER_IMPL_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_SERIALIZER_IMPL_H_ // NOLINT + +#include <libxml/tree.h> + +#include <string> +#include <unordered_map> + +#include "xmpmeta/xml/serializer.h" + +namespace photos_editing_formats { +namespace xml { + +// Writes properties, lists, and child nodes into an XML structure. +// +// Usage example: +// std::unordered_map<string, xmlNsPtr> namespaces; +// string device_name("Device"); +// string cameras_name("Cameras"); +// string camera_name("Camera"); +// string audio_name("Audio"); +// string image_name("Image"); +// PopulateNamespaces(&namespaces); +// DoSerialization(); +// +// // Serialization example. +// void DoSerialization() { +// xmlNodePtr device_node = xmlNewNode(nullptr, device_name); +// Serializer device_serializer(namespaces, device_node); +// +// std::unique_ptr<Serializer> cameras_serializer = +// serializer->CreateListSerializer(cameras_name); +// for (XdmCamera *camera : camera_list_) { +// std::unique_ptr<Serializer> camera_serializer = +// cameras_serializer->CreateItemSerializer("Device", camera_name); +// success &= camera->Serialize(camera_serializer.get()); +// +// // Serialize Audio. +// std::unique_ptr<Serializer> audio_serializer = +// camera_serializer->CreateSerializer("Camera", audio_name); +// audio_serializer->WriteProperty(camera_name, "Data", audio_data); +// audio_serializer->WriteProperty(camera_name, "Mime", "audio/mp4"); +// +// // Serialize Image. +// std::unique_ptr<Serializer> image_serializer = +// camera_serializer->CreateSerializer("Camera", image_name); +// image_serializer->WriteProperty(image_name, "Data", image_data); +// image_serializer->WriteProperty(image_name, "Mime", "image/jpeg"); +// +// // Serialize ImagingModel. +// std::unique_ptr<Serializer> imaging_model_serializer = +// camera_serializer->CreateSerializer("Camera", "ImagingModel"); +// std::unique_ptr<Serializer> equirect_model_serializer = +// imaging_model_serializer->CreateSerializer("Camera", +// "EquirectModel"); +// // Serializer equirect model fields here. +// } +// } +// +// Resulting XML structure: +// /* +// * <Device> +// * <Device:Cameras> +// * <rdf:Seq> +// * <rdf:li> +// * <Device:Camera> +// * <Camera:Audio Audio:Mime="audio/mp4" Audio:Data="DataValue"/> +// * <Camera:Image Image:Mime="image/jpeg" Image:Data="DataValue"/> +// * <Camera:ImagingModel> +// * <Camera:EquirectModel ...properties/> +// * </Camera:ImagingModel> +// * </Device:Camera> +// * </rdf:li> +// * </rdf:Seq> +// * </Device:Cameras> +// * </Device> +// */ +// +// // Namespace population example. +// void PopulateNamespaces(std::unordered_map<string, xmlNsPtr>* namespaces) { +// xmlNsPtr device_ns = +// xmlNewNs(nullptr, ToXmlChar("http://ns.xdm.org/photos/1.0/device") +// ToXmlChar(device_name.data())); +// xmlNsPtr camera_ns = +// xmlNewNs(nullptr, ToXmlChar("http://ns.xdm.org/photos/1.0/camera") +// ToXmlChar(camera_name.data())); +// xmlNsPtr audio_ns = +// xmlNewNs(nullptr, ToXmlChar("http://ns.xdm.org/photos/1.0/audio") +// ToXmlChar(audio_name.data())); +// xmlNsPtr image_ns = +// xmlNewNs(nullptr, ToXmlChar("http://ns.xdm.org/photos/1.0/image") +// ToXmlChar(image_name.data())); +// namespaces->insert(device_name, device_ns); +// namespaces->insert(camera_name, camera_ns); +// namespaces->insert(audio_name, audio_ns); +// namespaces->insert(image_name, image_ns); +// } + +class SerializerImpl : public Serializer { + public: + // Constructor. + // The prefix map is required if one of the CreateSerializer methods will be + // called on this object. In particular, the RDF namespace must be present in + // the prefix map if CreateItemSerializer or CreateListSerializer will be + // called. + // The namespaces map serves to keep XML namespace creation out of this + // Serializer, to simplify memory management issues. Note that the libxml + // xmlDocPtr will own all namespace and node pointers. + // The namespaces parameter is a map of node names to full namespaces. + // This contains all the namespaces (nodes and properties) that will be used + // in serialization. + // The node parameter is the caller node. This will be the node in which + // serialization takes place in WriteProperties. + SerializerImpl(const std::unordered_map<string, xmlNsPtr>& namespaces, + xmlNodePtr node); + + // Returns a new Serializer for an object that is part of an rdf:Seq list + // of objects. + // The parent serializer must be created with CreateListSerializer. + std::unique_ptr<Serializer> CreateItemSerializer( + const string& prefix, const string& item_name) const override; + + // Returns a new Serializer for a list of objects that correspond to an + // rdf:Seq XML node, where each object is to be serialized as a child node of + // every rdf:li node in the list. + // The serializer is created on an rdf:Seq node, which is the child of a + // newly created XML node with the name list_name. + std::unique_ptr<Serializer> CreateListSerializer( + const string& prefix, const string& list_name) const override; + + // Creates a serializer from the current serializer. + // @param node_name The name of the caller node. This will be the parent of + // any new nodes or properties set by this serializer. + std::unique_ptr<Serializer> CreateSerializer( + const string& node_ns_name, const string& node_name) const override; + + // Writes the property into the current node, prefixed with prefix if it + // has a corresponding namespace href in namespaces_, fails otherwise. + // Returns true if serialization is successful. + // If prefix is empty, the property will not be set on an XML namespace. + // name must not be empty. + // value may be empty. + bool WriteBoolProperty(const string& prefix, const string& name, + bool value) const override; + bool WriteProperty(const string& prefix, const string& name, + const string& value) const override; + + // Writes the collection of numbers into a child rdf:Seq node. + bool WriteIntArray(const string& prefix, const string& array_name, + const std::vector<int>& values) const override; + bool WriteDoubleArray(const string& prefix, const string& array_name, + const std::vector<double>& values) const override; + + // Class-specific methods. + // Constructs a serializer object and writes the xmlNsPtr objects in + // namespaces_ to node_. + static std::unique_ptr<SerializerImpl> FromDataAndSerializeNamespaces( + const std::unordered_map<string, xmlNsPtr>& namespaces, xmlNodePtr node); + + // Disallow copying. + SerializerImpl(const SerializerImpl&) = delete; + void operator=(const SerializerImpl&) = delete; + + private: + // Writes the xmlNsPtr objects in namespaces_ to node_. + // Modifies namespaces_ by setting each xmlNsPtr's next pointer to the + // subsequent entry in the collection. + bool SerializeNamespaces(); + + xmlNodePtr node_; + std::unordered_map<string, xmlNsPtr> namespaces_; +}; + +} // namespace xml +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_SERIALIZER_IMPL_H_ // NOLINT diff --git a/internal/xmpmeta/xml/utils.cc b/internal/xmpmeta/xml/utils.cc new file mode 100644 index 0000000..87bf0b1 --- /dev/null +++ b/internal/xmpmeta/xml/utils.cc @@ -0,0 +1,77 @@ +#include "xmpmeta/xml/utils.h" + +#include "android-base/logging.h" +#include "base/port.h" +#include "xmpmeta/xml/const.h" +#include "xmpmeta/xml/search.h" + +namespace photos_editing_formats { +namespace xml { + +xmlNodePtr GetFirstDescriptionElement(const xmlDocPtr parent) { + return DepthFirstSearch(parent, XmlConst::RdfDescription()); +} + +xmlNodePtr GetFirstSeqElement(xmlDocPtr parent) { + // DepthFirstSearch will perform the null check. + return DepthFirstSearch(parent, XmlConst::RdfSeq()); +} + +// Returns the first rdf:Seq element found in the given node. +xmlNodePtr GetFirstSeqElement(xmlNodePtr parent) { + // DepthFirstSearch will perform the null check. + return DepthFirstSearch(parent, XmlConst::RdfSeq()); +} + +// Returns the ith (zero-indexed) element in the given node. +// {@code parent} is an rdf:Seq node. +xmlNodePtr GetElementAt(xmlNodePtr node, int index) { + if (node == nullptr || index < 0) { + LOG(ERROR) << "Node was null or index was negative"; + return nullptr; + } + const string node_name = FromXmlChar(node->name); + if (strcmp(node_name.c_str(), XmlConst::RdfSeq())) { + LOG(ERROR) << "Node is not an rdf:Seq node, was " << node_name; + return nullptr; + } + int i = 0; + for (xmlNodePtr child = node->children; child != nullptr && i <= index; + child = child->next) { + if (strcmp(FromXmlChar(child->name), XmlConst::RdfLi())) { + // This is not an rdf:li node. This can occur because the node's content + // is also treated as a node, and these should be ignored. + continue; + } + if (i == index) { + return child; + } + i++; + } + return nullptr; +} + +const string GetLiNodeContent(xmlNodePtr node) { + string value; + if (node == nullptr || strcmp(FromXmlChar(node->name), XmlConst::RdfLi())) { + LOG(ERROR) << "Node is null or is not an rdf:li node"; + return value; + } + xmlChar* node_content = xmlNodeGetContent(node); + value = FromXmlChar(node_content); + free(node_content); + return value; +} + +const string XmlDocToString(const xmlDocPtr doc) { + xmlChar* xml_doc_contents; + int doc_size = 0; + xmlDocDumpFormatMemoryEnc(doc, &xml_doc_contents, &doc_size, + XmlConst::EncodingStr(), 1); + const string xml_doc_string(FromXmlChar(xml_doc_contents)); + xmlFree(xml_doc_contents); + return xml_doc_string; +} + +} // namespace xml +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/xml/utils.h b/internal/xmpmeta/xml/utils.h new file mode 100644 index 0000000..f0f99e3 --- /dev/null +++ b/internal/xmpmeta/xml/utils.h @@ -0,0 +1,54 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_UTILS_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_UTILS_H_ // NOLINT + +#include <libxml/tree.h> + +#include <string> + +#include "base/port.h" + +namespace photos_editing_formats { +namespace xml { + +// Convenience function to convert an xmlChar* to a char* +inline const char* FromXmlChar(const xmlChar* in) { + return reinterpret_cast<const char*>(in); +} + +// Convenience function to convert a char* to an xmlChar*. +inline const xmlChar* ToXmlChar(const char* in) { + return reinterpret_cast<const xmlChar*>(in); +} + +// Returns the first rdf:Description node; null if not found. +xmlNodePtr GetFirstDescriptionElement(xmlDocPtr parent); + +// Returns the first rdf:Seq element found in the XML document. +xmlNodePtr GetFirstSeqElement(xmlDocPtr parent); + +// Returns the first rdf:Seq element found in the given node. +// Returns {@code parent} if that is itself an rdf:Seq node. +xmlNodePtr GetFirstSeqElement(xmlNodePtr parent); + +// Returns the ith (zero-indexed) rdf:li node in the given rdf:Seq node. +// Returns null if either of {@code index} < 0, {@code node} is null, or is +// not an rdf:Seq node. +xmlNodePtr GetElementAt(xmlNodePtr node, int index); + +// Returns the value in an rdf:li node. This is for a node whose value +// does not have a name, e.g. <rdf:li>value</rdf:li>. +// If the given rdf:li node has a nested node, it returns the string +// representation of the contents of those nodes, which replaces the XML +// tags with one whitespace character for each tag character. +// This is treated as undefined behavior; it is the caller's responsibility +// to remove any whitespace and newlines. +const string GetLiNodeContent(xmlNodePtr node); + +// Returns the given XML doc serialized to a string. +// For debugging purposes. +const string XmlDocToString(const xmlDocPtr doc); + +} // namespace xml +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_XMPMETA_XML_UTILS_H_ // NOLINT diff --git a/internal/xmpmeta/xmp_const.cc b/internal/xmpmeta/xmp_const.cc new file mode 100644 index 0000000..69fc3a9 --- /dev/null +++ b/internal/xmpmeta/xmp_const.cc @@ -0,0 +1,38 @@ +#include "xmpmeta/xmp_const.h" + +namespace photos_editing_formats { + +// XMP namespace constants. +const char* XmpConst::Namespace() { return "adobe:ns:meta/"; } + +const char* XmpConst::NamespacePrefix() { return "x"; } + +const char* XmpConst::NodeName() { return "xmpmeta"; } + +const char* XmpConst::AdobePropName() { return "xmptk"; } + +const char* XmpConst::AdobePropValue() { return "Adobe XMP"; } + +const char* XmpConst::NoteNamespace() { + return "http://ns.adobe.com/xmp/note/"; +} + +// XMP headers. +const char* XmpConst::Header() { return "http://ns.adobe.com/xap/1.0/"; } + +const char* XmpConst::ExtensionHeader() { + return "http://ns.adobe.com/xmp/extension/"; +} + +const char* XmpConst::HasExtensionPrefix() { return "xmpNote"; } + +const char* XmpConst::HasExtension() { return "HasExtendedXMP"; } + +// Sizes. +const int XmpConst::ExtensionHeaderOffset() { return 8; } + +const int XmpConst::MaxBufferSize() { return 65502; } + +const int XmpConst::ExtendedMaxBufferSize() { return 65458; } + +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/xmp_data.cc b/internal/xmpmeta/xmp_data.cc new file mode 100644 index 0000000..046052b --- /dev/null +++ b/internal/xmpmeta/xmp_data.cc @@ -0,0 +1,28 @@ +#include "xmpmeta/xmp_data.h" + +namespace photos_editing_formats { + +XmpData::XmpData() : xmp_(nullptr), xmp_extended_(nullptr) {} + +XmpData::~XmpData() { Reset(); } + +void XmpData::Reset() { + if (xmp_) { + xmlFreeDoc(xmp_); + xmp_ = nullptr; + } + if (xmp_extended_) { + xmlFreeDoc(xmp_extended_); + xmp_extended_ = nullptr; + } +} + +const xmlDocPtr XmpData::StandardSection() const { return xmp_; } + +xmlDocPtr* XmpData::MutableStandardSection() { return &xmp_; } + +const xmlDocPtr XmpData::ExtendedSection() const { return xmp_extended_; } + +xmlDocPtr* XmpData::MutableExtendedSection() { return &xmp_extended_; } + +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/xmp_parser.cc b/internal/xmpmeta/xmp_parser.cc new file mode 100644 index 0000000..4ce8991 --- /dev/null +++ b/internal/xmpmeta/xmp_parser.cc @@ -0,0 +1,333 @@ +#include "xmpmeta/xmp_parser.h" + +#include <algorithm> +#include <cstring> +#include <sstream> +#include <stack> + +#include "android-base/logging.h" +#include "strings/case.h" +#include "strings/numbers.h" +#include "xmpmeta/base64.h" +#include "xmpmeta/jpeg_io.h" +#include "xmpmeta/xml/const.h" +#include "xmpmeta/xml/deserializer_impl.h" +#include "xmpmeta/xml/search.h" +#include "xmpmeta/xml/utils.h" +#include "xmpmeta/xmp_const.h" + +using photos_editing_formats::xml::DepthFirstSearch; +using photos_editing_formats::xml::DeserializerImpl; +using photos_editing_formats::xml::FromXmlChar; +using photos_editing_formats::xml::GetFirstDescriptionElement; + +namespace photos_editing_formats { +namespace { + +const char kJpgExtension[] = "jpg"; +const char kJpegExtension[] = "jpeg"; + +bool BoolStringToBool(const string& bool_str, bool* value) { + if (dynamic_depth::StringCaseEqual(bool_str, "true")) { + *value = true; + return true; + } + if (dynamic_depth::StringCaseEqual(bool_str, "false")) { + *value = false; + return true; + } + return false; +} + +// Converts string_property to the type T. +template <typename T> +bool ConvertStringPropertyToType(const string& string_property, T* value); + +// Gets the end of the XMP meta content. If there is no packet wrapper, returns +// data.length, otherwise returns 1 + the position of last '>' without '?' +// before it. Usually the packet wrapper end is "<?xpacket end="w"?>. +size_t GetXmpContentEnd(const string& data) { + if (data.empty()) { + return 0; + } + for (size_t i = data.size() - 1; i >= 1; --i) { + if (data[i] == '>') { + if (data[i - 1] != '?') { + return i + 1; + } + } + } + // It should not reach here for a valid XMP meta. + LOG(WARNING) << "Failed to find the end of the XMP meta content."; + return data.size(); +} + +// True if 's' starts with substring 'x'. +bool StartsWith(const string& s, const string& x) { + return s.size() >= x.size() && !s.compare(0, x.size(), x); +} +// True if 's' ends with substring 'x'. +bool EndsWith(const string& s, const string& x) { + return s.size() >= x.size() && !s.compare(s.size() - x.size(), x.size(), x); +} + +// Parses the first valid XMP section. Any other valid XMP section will be +// ignored. +bool ParseFirstValidXMPSection(const std::vector<Section>& sections, + XmpData* xmp) { + for (const Section& section : sections) { + if (StartsWith(section.data, XmpConst::Header())) { + const size_t end = GetXmpContentEnd(section.data); + // Increment header length by 1 for the null termination. + const size_t header_length = strlen(XmpConst::Header()) + 1; + // Check for integer underflow before subtracting. + if (header_length >= end) { + LOG(ERROR) << "Invalid content length: " + << static_cast<int>(end - header_length); + return false; + } + const size_t content_length = end - header_length; + // header_length is guaranteed to be <= data.size due to the if condition + // above. If this contract changes we must add an additonal check. + const char* content_start = §ion.data[header_length]; + // xmlReadMemory requires an int. Before casting size_t to int we must + // check for integer overflow. + if (content_length > INT_MAX) { + LOG(ERROR) << "First XMP section too large, size: " << content_length; + return false; + } + *xmp->MutableStandardSection() = xmlReadMemory( + content_start, static_cast<int>(content_length), nullptr, nullptr, 0); + if (xmp->StandardSection() == nullptr) { + LOG(WARNING) << "Failed to parse standard section."; + return false; + } + return true; + } + } + return false; +} + +// Collects the extended XMP sections with the given name into a string. Other +// sections will be ignored. +string GetExtendedXmpSections(const std::vector<Section>& sections, + const string& section_name) { + string extended_header = XmpConst::ExtensionHeader(); + extended_header += '\0' + section_name; + // section_name is dynamically extracted from the xml file and can have an + // arbitrary size. Check for integer overflow before addition. + if (extended_header.size() > SIZE_MAX - XmpConst::ExtensionHeaderOffset()) { + return ""; + } + const size_t section_start_offset = + extended_header.size() + XmpConst::ExtensionHeaderOffset(); + + // Compute the size of the buffer to parse the extended sections. + std::vector<const Section*> xmp_sections; + std::vector<size_t> xmp_end_offsets; + size_t buffer_size = 0; + for (const Section& section : sections) { + if (extended_header.empty() || StartsWith(section.data, extended_header)) { + const size_t end_offset = section.data.size(); + const size_t section_size = end_offset - section_start_offset; + if (end_offset < section_start_offset || + section_size > SIZE_MAX - buffer_size) { + return ""; + } + buffer_size += section_size; + xmp_sections.push_back(§ion); + xmp_end_offsets.push_back(end_offset); + } + } + + // Copy all the relevant sections' data into a buffer. + string buffer(buffer_size, '\0'); + if (buffer.size() != buffer_size) { + return ""; + } + size_t offset = 0; + for (int i = 0; i < xmp_sections.size(); ++i) { + const Section* section = xmp_sections[i]; + const size_t length = xmp_end_offsets[i] - section_start_offset; + std::copy_n(§ion->data[section_start_offset], length, &buffer[offset]); + offset += length; + } + return buffer; +} + +// Parses the extended XMP sections with the given name. All other sections +// will be ignored. +bool ParseExtendedXmpSections(const std::vector<Section>& sections, + const string& section_name, XmpData* xmp_data) { + const string extended_sections = + GetExtendedXmpSections(sections, section_name); + // xmlReadMemory requires an int. Before casting size_t to int we must check + // for integer overflow. + if (extended_sections.size() > INT_MAX) { + LOG(WARNING) << "Extended sections too large, size: " + << extended_sections.size(); + return false; + } + *xmp_data->MutableExtendedSection() = xmlReadMemory( + extended_sections.data(), static_cast<int>(extended_sections.size()), + nullptr, nullptr, XML_PARSE_HUGE); + if (xmp_data->ExtendedSection() == nullptr) { + LOG(WARNING) << "Failed to parse extended sections."; + return false; + } + return true; +} + +// Extracts a XmpData from a JPEG image stream. +bool ExtractXmpMeta(const bool skip_extended, std::istream* file, + XmpData* xmp_data) { + // We cannot use CHECK because this is ported to AOSP. + assert(xmp_data != nullptr); // NOLINT + xmp_data->Reset(); + + ParseOptions parse_options; + parse_options.read_meta_only = true; + if (skip_extended) { + parse_options.section_header = XmpConst::Header(); + parse_options.section_header_return_first = true; + } + const std::vector<Section> sections = Parse(parse_options, file); + if (sections.empty()) { + LOG(WARNING) << "No sections found."; + return false; + } + + if (!ParseFirstValidXMPSection(sections, xmp_data)) { + LOG(WARNING) << "Could not parse first section."; + return false; + } + if (skip_extended) { + return true; + } + string extension_name; + DeserializerImpl deserializer( + GetFirstDescriptionElement(xmp_data->StandardSection())); + if (!deserializer.ParseString(XmpConst::HasExtensionPrefix(), + XmpConst::HasExtension(), &extension_name)) { + // No extended sections present, so nothing to parse. + return true; + } + if (!ParseExtendedXmpSections(sections, extension_name, xmp_data)) { + LOG(WARNING) << "Extended sections present, but could not be parsed."; + return false; + } + return true; +} + +// Extracts the specified string attribute. +bool GetStringProperty(const xmlNodePtr node, const char* prefix, + const char* property, string* value) { + const xmlDocPtr doc = node->doc; + for (const _xmlAttr* attribute = node->properties; attribute != nullptr; + attribute = attribute->next) { + if (attribute->ns && + strcmp(FromXmlChar(attribute->ns->prefix), prefix) == 0 && + strcmp(FromXmlChar(attribute->name), property) == 0) { + xmlChar* attribute_string = + xmlNodeListGetString(doc, attribute->children, 1); + *value = FromXmlChar(attribute_string); + xmlFree(attribute_string); + return true; + } + } + LOG(WARNING) << "Could not find string attribute: " << property; + return false; +} + +// Reads the contents of a node. +// E.g. <prefix:node_name>Contents Here</prefix:node_name> +bool ReadNodeContent(const xmlNodePtr node, const char* prefix, + const char* node_name, string* value) { + auto* element = DepthFirstSearch(node, node_name); + if (element == nullptr) { + return false; + } + if (prefix != nullptr && + (element->ns == nullptr || element->ns->prefix == nullptr || + strcmp(FromXmlChar(element->ns->prefix), prefix) != 0)) { + return false; + } + xmlChar* node_content = xmlNodeGetContent(element); + *value = FromXmlChar(node_content); + free(node_content); + return true; +} + +template <typename T> +bool ConvertStringPropertyToType(const string& string_property, T* value) { + QCHECK(value) << "Cannot call this method on a generic type"; + return false; +} + +template <> +bool ConvertStringPropertyToType<bool>(const string& string_property, + bool* value) { + return BoolStringToBool(string_property, value); +} + +template <> +bool ConvertStringPropertyToType<double>(const string& string_property, + double* value) { + *value = std::stod(string_property); + return true; +} + +template <> +bool ConvertStringPropertyToType<int>(const string& string_property, + int* value) { + *value = 0; + for (int i = 0; i < string_property.size(); ++i) { + if (!isdigit(string_property[i])) { + return false; + } + } + + *value = std::atoi(string_property.c_str()); // NOLINT + return true; +} + +template <> +bool ConvertStringPropertyToType<int64>(const string& string_property, + int64* value) { + *value = std::stol(string_property); + return true; +} + +} // namespace + +bool ReadXmpHeader(const string& filename, const bool skip_extended, + XmpData* xmp_data) { + string filename_lower = filename; + std::transform(filename_lower.begin(), filename_lower.end(), + filename_lower.begin(), ::tolower); + if (!EndsWith(filename_lower, kJpgExtension) && + !EndsWith(filename_lower, kJpegExtension)) { + LOG(WARNING) << "XMP parse: only JPEG file is supported"; + return false; + } + + std::ifstream file(filename.c_str(), std::ios::binary); + if (!file.is_open()) { + LOG(WARNING) << " Could not read file: " << filename; + return false; + } + return ExtractXmpMeta(skip_extended, &file, xmp_data); +} + +bool ReadXmpFromMemory(const string& jpeg_contents, const bool skip_extended, + XmpData* xmp_data) { + std::istringstream stream(jpeg_contents); + return ExtractXmpMeta(skip_extended, &stream, xmp_data); +} + +bool ReadXmpHeader(std::istream* input_stream, bool skip_extended, + XmpData* xmp_data) { + return ExtractXmpMeta(skip_extended, input_stream, xmp_data); +} + +} // namespace photos_editing_formats diff --git a/internal/xmpmeta/xmp_writer.cc b/internal/xmpmeta/xmp_writer.cc new file mode 100644 index 0000000..73e5a65 --- /dev/null +++ b/internal/xmpmeta/xmp_writer.cc @@ -0,0 +1,350 @@ +#include "xmpmeta/xmp_writer.h" + +#include <libxml/tree.h> +#include <libxml/xmlIO.h> +#include <libxml/xmlstring.h> + +#include <fstream> +#include <sstream> +#include <string> +#include <vector> + +#include "android-base/logging.h" +#include "xmpmeta/jpeg_io.h" +#include "xmpmeta/md5.h" +#include "xmpmeta/xml/const.h" +#include "xmpmeta/xml/utils.h" +#include "xmpmeta/xmp_const.h" +#include "xmpmeta/xmp_data.h" +#include "xmpmeta/xmp_parser.h" + +using photos_editing_formats::xml::FromXmlChar; +using photos_editing_formats::xml::ToXmlChar; +using photos_editing_formats::xml::XmlConst; + +namespace photos_editing_formats { +namespace { + +const char kXmlStartTag = '<'; + +const char kCEmptyString[] = "\x00"; +const int kXmlDumpFormat = 1; +const int kInvalidIndex = -1; + +// True if 's' starts with substring 'x'. +bool StartsWith(const string& s, const string& x) { + return s.size() >= x.size() && !s.compare(0, x.size(), x); +} +// True if 's' ends with substring 'x'. +bool EndsWith(const string& s, const string& x) { + return s.size() >= x.size() && !s.compare(s.size() - x.size(), x.size(), x); +} + +// Creates the outer rdf:RDF node for XMP. +xmlNodePtr CreateXmpRdfNode() { + xmlNodePtr rdf_node = xmlNewNode(nullptr, ToXmlChar(XmlConst::RdfNodeName())); + xmlNsPtr rdf_ns = xmlNewNs(rdf_node, ToXmlChar(XmlConst::RdfNodeNs()), + ToXmlChar(XmlConst::RdfPrefix())); + xmlSetNs(rdf_node, rdf_ns); + return rdf_node; +} + +// Creates the root node for XMP. +xmlNodePtr CreateXmpRootNode() { + xmlNodePtr root_node = xmlNewNode(nullptr, ToXmlChar(XmpConst::NodeName())); + xmlNsPtr root_ns = xmlNewNs(root_node, ToXmlChar(XmpConst::Namespace()), + ToXmlChar(XmpConst::NamespacePrefix())); + xmlSetNs(root_node, root_ns); + xmlSetNsProp(root_node, root_ns, ToXmlChar(XmpConst::AdobePropName()), + ToXmlChar(XmpConst::AdobePropValue())); + return root_node; +} + +// Creates a new XMP metadata section, with an x:xmpmeta element wrapping +// rdf:RDF and rdf:Description child elements. This is the equivalent of +// createXMPMeta in geo/lightfield/metadata/XmpUtils.java +xmlDocPtr CreateXmpSection() { + xmlDocPtr xmp_meta = xmlNewDoc(ToXmlChar(XmlConst::Version())); + + xmlNodePtr root_node = CreateXmpRootNode(); + xmlNodePtr rdf_node = CreateXmpRdfNode(); + xmlNodePtr description_node = + xmlNewNode(nullptr, ToXmlChar(XmlConst::RdfDescription())); + xmlNsPtr rdf_prefix_ns = + xmlNewNs(description_node, nullptr, ToXmlChar(XmlConst::RdfPrefix())); + xmlSetNs(description_node, rdf_prefix_ns); + + // rdf:about is mandatory. + xmlSetNsProp(description_node, rdf_node->ns, ToXmlChar(XmlConst::RdfAbout()), + ToXmlChar("")); + + // Align nodes into the proper hierarchy. + xmlAddChild(rdf_node, description_node); + xmlAddChild(root_node, rdf_node); + xmlDocSetRootElement(xmp_meta, root_node); + + return xmp_meta; +} + +void WriteIntTo4Bytes(int integer, std::ostream* output_stream) { + output_stream->put((integer >> 24) & 0xff); + output_stream->put((integer >> 16) & 0xff); + output_stream->put((integer >> 8) & 0xff); + output_stream->put(integer & 0xff); +} + +// Serializes an XML document to a string. +void SerializeMeta(const xmlDocPtr parent, string* serialized_value) { + if (parent == nullptr || parent->children == nullptr) { + LOG(WARNING) << "Nothing to serialize, either XML doc is null or it has " + << "no elements"; + return; + } + + std::ostringstream serialized_stream; + xmlChar* xml_doc_contents; + int doc_size = 0; + xmlDocDumpFormatMemoryEnc(parent, &xml_doc_contents, &doc_size, + XmlConst::EncodingStr(), kXmlDumpFormat); + const char* xml_doc_string = FromXmlChar(xml_doc_contents); + + // Find the index of the second "<" so we can discard the first element, + // which is <?xml version...>, so start searching after the first "<". XMP + // starts directly afterwards. + const int xmp_start_idx = + static_cast<int>(strchr(&xml_doc_string[2], kXmlStartTag) - + xml_doc_string) - + 1; + serialized_stream.write(&xml_doc_string[xmp_start_idx], + doc_size - xmp_start_idx); + xmlFree(xml_doc_contents); + *serialized_value = serialized_stream.str(); +} + +// TODO(miraleung): Switch to different library for Android if needed. +const string GetGUID(const string& to_hash) { return MD5Hash(to_hash); } + +// Creates the standard XMP section. +void CreateStandardSectionXmpString(const string& buffer, string* value) { + std::ostringstream data_stream; + data_stream.write(XmpConst::Header(), strlen(XmpConst::Header())); + data_stream.write(kCEmptyString, 1); + data_stream.write(buffer.c_str(), buffer.length()); + *value = data_stream.str(); +} + +// Creates the extended XMP section. +void CreateExtendedSections(const string& buffer, + std::vector<Section>* extended_sections) { + string guid = GetGUID(buffer); + // Increment by 1 for the null byte in the middle. + const int header_length = + static_cast<int>(strlen(XmpConst::ExtensionHeader()) + 1 + guid.length()); + const int buffer_length = static_cast<int>(buffer.length()); + const int overhead = header_length + XmpConst::ExtensionHeaderOffset(); + const int num_sections = + buffer_length / (XmpConst::ExtendedMaxBufferSize() - overhead) + 1; + for (int i = 0, position = 0; i < num_sections; ++i) { + const int section_size = + std::min(static_cast<int>(buffer_length - position + overhead), + XmpConst::ExtendedMaxBufferSize()); + const int bytes_from_buffer = section_size - overhead; + + // Header and GUID. + std::ostringstream data_stream; + data_stream.write(XmpConst::ExtensionHeader(), + strlen(XmpConst::ExtensionHeader())); + data_stream.write(kCEmptyString, 1); + data_stream.write(guid.c_str(), guid.length()); + + // Total buffer length. + WriteIntTo4Bytes(buffer_length, &data_stream); + // Current position. + WriteIntTo4Bytes(position, &data_stream); + // Data + data_stream.write(&buffer[position], bytes_from_buffer); + position += bytes_from_buffer; + + extended_sections->push_back(Section(data_stream.str())); + } +} + +int InsertStandardXMPSection(const string& buffer, + std::vector<Section>* sections) { + if (buffer.length() > XmpConst::MaxBufferSize()) { + LOG(WARNING) << "The standard XMP section (at size " << buffer.length() + << ") cannot have a size larger than " + << XmpConst::MaxBufferSize() << " bytes"; + return kInvalidIndex; + } + string value; + CreateStandardSectionXmpString(buffer, &value); + Section xmp_section(value); + // If we can find the old XMP section, replace it with the new one + for (int index = 0; index < sections->size(); ++index) { + if (sections->at(index).IsMarkerApp1() && + StartsWith(sections->at(index).data, XmpConst::Header())) { + // Replace with the new XMP data. + sections->at(index) = xmp_section; + return index; + } + } + // If the first section is EXIF, insert XMP data after it. + // Otherwise, make XMP data the first section. + const int position = + (!sections->empty() && sections->at(0).IsMarkerApp1()) ? 1 : 0; + sections->emplace(sections->begin() + position, xmp_section); + return position; +} + +// Position is the index in the Section vector where the extended sections +// will be inserted. +void InsertExtendedXMPSections(const string& buffer, int position, + std::vector<Section>* sections) { + std::vector<Section> extended_sections; + CreateExtendedSections(buffer, &extended_sections); + sections->insert(sections->begin() + position, extended_sections.begin(), + extended_sections.end()); +} + +// Returns true if the respective sections in xmp_data and their serialized +// counterparts are (correspondingly) not null and not empty. +bool XmpSectionsAndSerializedDataValid(const XmpData& xmp_data, + const string& main_buffer, + const string& extended_buffer) { + // Standard section and its serialized counterpart cannot be null/empty. + // Extended section can be null XOR the extended buffer can be empty. + const bool extended_is_consistent = + ((xmp_data.ExtendedSection() == nullptr) == extended_buffer.empty()); + const bool is_valid = (xmp_data.StandardSection() != nullptr) && + !main_buffer.empty() && extended_is_consistent; + if (!is_valid) { + LOG(ERROR) << "XMP sections Xor their serialized counterparts are empty"; + } + return is_valid; +} + +// Updates a list of JPEG sections with serialized XMP data. +bool UpdateSections(const string& main_buffer, const string& extended_buffer, + std::vector<Section>* sections) { + if (main_buffer.empty()) { + LOG(WARNING) << "Main section was empty"; + return false; + } + + // Update the list of sections with the new standard XMP section. + const int main_index = InsertStandardXMPSection(main_buffer, sections); + if (main_index < 0) { + LOG(WARNING) << "Could not find a valid index for inserting the " + << "standard sections"; + return false; + } + + // Insert the extended section right after the main section. + if (!extended_buffer.empty()) { + InsertExtendedXMPSections(extended_buffer, main_index + 1, sections); + } + return true; +} + +void LinkXmpStandardAndExtendedSections(const string& extended_buffer, + xmlDocPtr standard_section) { + xmlNodePtr description_node = + xml::GetFirstDescriptionElement(standard_section); + xmlNsPtr xmp_note_ns_ptr = + xmlNewNs(description_node, ToXmlChar(XmpConst::NoteNamespace()), + ToXmlChar(XmpConst::HasExtensionPrefix())); + const string extended_id = GetGUID(extended_buffer); + xmlSetNsProp(description_node, xmp_note_ns_ptr, + ToXmlChar(XmpConst::HasExtension()), + ToXmlChar(extended_id.c_str())); + xmlUnsetProp(description_node, ToXmlChar(XmpConst::HasExtension())); +} + +} // namespace + +std::unique_ptr<XmpData> CreateXmpData(bool create_extended) { + std::unique_ptr<XmpData> xmp_data(new XmpData()); + *xmp_data->MutableStandardSection() = CreateXmpSection(); + if (create_extended) { + *xmp_data->MutableExtendedSection() = CreateXmpSection(); + } + return xmp_data; +} + +bool WriteLeftEyeAndXmpMeta(const string& left_data, const string& filename, + const XmpData& xmp_data) { + std::istringstream input_jpeg_stream(left_data); + std::ofstream output_jpeg_stream; + output_jpeg_stream.open(filename, std::ostream::out); + bool success = WriteLeftEyeAndXmpMeta(filename, xmp_data, &input_jpeg_stream, + &output_jpeg_stream); + output_jpeg_stream.close(); + return success; +} + +bool WriteLeftEyeAndXmpMeta(const string& filename, const XmpData& xmp_data, + std::istringstream* input_jpeg_stream, + std::ofstream* output_jpeg_stream) { + if (input_jpeg_stream == nullptr || output_jpeg_stream == nullptr) { + LOG(ERROR) << "Input and output streams must both be non-null"; + return false; + } + + // Get a list of sections from the input stream. + ParseOptions parse_options; + std::vector<Section> sections = Parse(parse_options, input_jpeg_stream); + + string extended_buffer; + if (xmp_data.ExtendedSection() != nullptr) { + SerializeMeta(xmp_data.ExtendedSection(), &extended_buffer); + LinkXmpStandardAndExtendedSections(extended_buffer, + xmp_data.StandardSection()); + } + string main_buffer; + SerializeMeta(xmp_data.StandardSection(), &main_buffer); + + // Update the input sections with the XMP data. + if (!XmpSectionsAndSerializedDataValid(xmp_data, main_buffer, + extended_buffer) || + !UpdateSections(main_buffer, extended_buffer, §ions)) { + return false; + } + + // Write the sections to the output stream. + if (!output_jpeg_stream->is_open()) { + output_jpeg_stream->open(filename, std::ostream::out); + } + + WriteSections(sections, output_jpeg_stream); + return true; +} + +bool AddXmpMetaToJpegStream(std::istream* input_jpeg_stream, + const XmpData& xmp_data, + std::ostream* output_jpeg_stream) { + // Get a list of sections from the input stream. + ParseOptions parse_options; + std::vector<Section> sections = Parse(parse_options, input_jpeg_stream); + + string extended_buffer; + if (xmp_data.ExtendedSection() != nullptr) { + SerializeMeta(xmp_data.ExtendedSection(), &extended_buffer); + LinkXmpStandardAndExtendedSections(extended_buffer, + xmp_data.StandardSection()); + } + string main_buffer; + SerializeMeta(xmp_data.StandardSection(), &main_buffer); + + // Update the input sections with the XMP data. + if (!XmpSectionsAndSerializedDataValid(xmp_data, main_buffer, + extended_buffer) || + !UpdateSections(main_buffer, extended_buffer, §ions)) { + return false; + } + + WriteSections(sections, output_jpeg_stream); + return true; +} + +} // namespace photos_editing_formats |