summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEino-Ville Talvala <etalvala@google.com>2018-11-15 15:49:02 -0800
committerEino-Ville Talvala <etalvala@google.com>2018-11-15 16:07:24 -0800
commit09f199a694ef5b956cabc368e40ab5ca11c64044 (patch)
tree456d184a3817c8b6524a90fc2da60893a2fc1895
parent2d25fc4f6a7e7453f877958a2f3f59b6cc588ca4 (diff)
downloaddynamic_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
-rw-r--r--Android.bp52
-rw-r--r--LICENSE202
-rw-r--r--METADATA17
-rw-r--r--MODULE_LICENSE_APACHE20
l---------NOTICE1
-rw-r--r--OWNERS2
-rw-r--r--includes/dynamic_depth/app_info.h81
-rw-r--r--includes/dynamic_depth/camera.h126
-rw-r--r--includes/dynamic_depth/cameras.h53
-rw-r--r--includes/dynamic_depth/container.h52
-rw-r--r--includes/dynamic_depth/depth_map.h128
-rw-r--r--includes/dynamic_depth/device.h131
-rw-r--r--includes/dynamic_depth/dynamic_depth.h34
-rw-r--r--includes/dynamic_depth/earth_pose.h94
-rw-r--r--includes/dynamic_depth/image.h89
-rw-r--r--includes/dynamic_depth/imaging_model.h90
-rw-r--r--includes/dynamic_depth/item.h89
-rw-r--r--includes/dynamic_depth/light_estimate.h68
-rw-r--r--includes/dynamic_depth/plane.h93
-rw-r--r--includes/dynamic_depth/planes.h61
-rw-r--r--includes/dynamic_depth/point_cloud.h61
-rw-r--r--includes/dynamic_depth/pose.h98
-rw-r--r--includes/dynamic_depth/profile.h56
-rw-r--r--includes/dynamic_depth/profiles.h56
-rw-r--r--includes/dynamic_depth/vendor_info.h74
-rw-r--r--includes/dynamic_depth/version.h20
-rw-r--r--includes/xmpmeta/jpeg_io.h53
-rw-r--r--includes/xmpmeta/md5.h16
-rw-r--r--includes/xmpmeta/version.h26
-rw-r--r--includes/xmpmeta/xmp_const.h30
-rw-r--r--includes/xmpmeta/xmp_data.h34
-rw-r--r--includes/xmpmeta/xmp_parser.h27
-rw-r--r--includes/xmpmeta/xmp_writer.h43
-rw-r--r--includes/xmpmeta/xmpmeta.h11
-rw-r--r--internal/base/disable_warnings.h15
-rw-r--r--internal/base/integral_types.h113
-rw-r--r--internal/base/macros.h56
-rw-r--r--internal/base/port.h1511
-rw-r--r--internal/base/reenable_warnings.h9
-rw-r--r--internal/dynamic_depth/README.md8
-rw-r--r--internal/dynamic_depth/app_info.cc145
-rw-r--r--internal/dynamic_depth/camera.cc282
-rw-r--r--internal/dynamic_depth/cameras.cc102
-rw-r--r--internal/dynamic_depth/const.cc114
-rw-r--r--internal/dynamic_depth/const.h51
-rw-r--r--internal/dynamic_depth/container.cc122
-rw-r--r--internal/dynamic_depth/depth_map.cc362
-rw-r--r--internal/dynamic_depth/device.cc313
-rw-r--r--internal/dynamic_depth/dimension.h26
-rw-r--r--internal/dynamic_depth/dynamic_depth.cc125
-rw-r--r--internal/dynamic_depth/earth_pose.cc190
-rw-r--r--internal/dynamic_depth/element.h34
-rw-r--r--internal/dynamic_depth/image.cc193
-rw-r--r--internal/dynamic_depth/imaging_model.cc222
-rw-r--r--internal/dynamic_depth/item.cc139
-rw-r--r--internal/dynamic_depth/light_estimate.cc128
-rw-r--r--internal/dynamic_depth/plane.cc177
-rw-r--r--internal/dynamic_depth/planes.cc116
-rw-r--r--internal/dynamic_depth/point.h25
-rw-r--r--internal/dynamic_depth/point_cloud.cc164
-rw-r--r--internal/dynamic_depth/pose.cc176
-rw-r--r--internal/dynamic_depth/profile.cc138
-rw-r--r--internal/dynamic_depth/profiles.cc99
-rw-r--r--internal/dynamic_depth/vendor_info.cc108
-rw-r--r--internal/strings/ascii_ctype.cc114
-rw-r--r--internal/strings/ascii_ctype.h89
-rw-r--r--internal/strings/case.cc14
-rw-r--r--internal/strings/case.h44
-rw-r--r--internal/strings/escaping.cc592
-rw-r--r--internal/strings/escaping.h72
-rw-r--r--internal/strings/fastmem.h183
-rw-r--r--internal/strings/numbers.cc546
-rw-r--r--internal/strings/numbers.h168
-rw-r--r--internal/strings/util.h49
-rw-r--r--internal/xmpmeta/base64.cc85
-rw-r--r--internal/xmpmeta/base64.h39
-rw-r--r--internal/xmpmeta/file.cc59
-rw-r--r--internal/xmpmeta/file.h18
-rw-r--r--internal/xmpmeta/jpeg_io.cc194
-rw-r--r--internal/xmpmeta/md5.cc223
-rw-r--r--internal/xmpmeta/xml/const.cc33
-rw-r--r--internal/xmpmeta/xml/const.h29
-rw-r--r--internal/xmpmeta/xml/deserializer.h65
-rw-r--r--internal/xmpmeta/xml/deserializer_impl.cc321
-rw-r--r--internal/xmpmeta/xml/deserializer_impl.h93
-rw-r--r--internal/xmpmeta/xml/search.cc73
-rw-r--r--internal/xmpmeta/xml/search.h36
-rw-r--r--internal/xmpmeta/xml/serializer.h76
-rw-r--r--internal/xmpmeta/xml/serializer_impl.cc247
-rw-r--r--internal/xmpmeta/xml/serializer_impl.h177
-rw-r--r--internal/xmpmeta/xml/utils.cc77
-rw-r--r--internal/xmpmeta/xml/utils.h54
-rw-r--r--internal/xmpmeta/xmp_const.cc38
-rw-r--r--internal/xmpmeta/xmp_data.cc28
-rw-r--r--internal/xmpmeta/xmp_parser.cc333
-rw-r--r--internal/xmpmeta/xmp_writer.cc350
96 files changed, 11850 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..b015217
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,52 @@
+
+cc_defaults {
+ name: "libdynamic_depth-defaults",
+ cflags: [
+ "-DUNIX_ENV=1",
+ "-Werror",
+ "-Wno-reorder",
+ "-Wno-unused-parameter",
+ "-Wno-ignored-qualifiers",
+ "-Wno-macro-redefined",
+ ],
+ rtti: true,
+ cppflags: ["-fno-exceptions"],
+ clang: true,
+ sanitize: {
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
+
+cc_library_headers {
+ name: "libdynamic_depth-public_headers",
+ export_include_dirs: ["includes"],
+}
+
+cc_library_headers {
+ name: "libdynamic_depth-internal_headers",
+ export_include_dirs: ["internal"],
+}
+
+cc_library {
+ name: "libdynamic_depth",
+ defaults: ["libdynamic_depth-defaults"],
+ vendor_available: false,
+ header_libs: [
+ "libdynamic_depth-public_headers",
+ "libdynamic_depth-internal_headers",
+ ],
+ srcs: ["internal/**/*.cc"],
+ shared_libs: [
+ "libbase",
+ "libimage_io",
+ "libxml2",
+ ],
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..0021c00
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,17 @@
+name: "dynamic_depth"
+description:
+ "Dynamic depth is a library for embedding depth layers within a standard image file format"
+
+third_party {
+ url {
+ type: PIPER
+ value: "http://google3/photos/editing/formats/dynamic_depth"
+ }
+ version: "221702153"
+ last_upgrade_date {
+ year: 2018
+ month: 11
+ day: 15
+ }
+ license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 120000
index 0000000..7a694c9
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1 @@
+LICENSE \ No newline at end of file
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..63cde58
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+include platform/frameworks/av/camera:/OWNERS
+
diff --git a/includes/dynamic_depth/app_info.h b/includes/dynamic_depth/app_info.h
new file mode 100644
index 0000000..4a519b4
--- /dev/null
+++ b/includes/dynamic_depth/app_info.h
@@ -0,0 +1,81 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_APP_INFO_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_APP_INFO_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/element.h"
+#include "dynamic_depth/item.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+/**
+ * A AppInfo element for a Dynamic Depth device.
+ */
+class AppInfo : public Element {
+ public:
+ // 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: ("AppInfo", "http://ns.google.com/photos/dd/1.0/appinfo/")
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ // Serializes this object.
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates an AppInfo from the given fields. Returns null if the version
+ // field is empty and [item_uri is empty and items is null].
+ // Params' descriptions:
+ // application is the name of the application that created the content.
+ // version is the application's version for the content.
+ // data is the optional payload associated with the given app. If this field
+ // is not empty but item_uri is empty or items is null, then the data will
+ // not be serialized.
+ // item_uri is the Container URI of the file that contains the content.
+ // application, and at least one of version or item_uri, must not be
+ // empty.
+ // items is the list of items where the serialized data is stored. It is the
+ // caller's responsibility to use items to construct a Container, and
+ // ensure that it is serialized along with this Image element. Data will
+ // not be serialized if this field is null.
+ static std::unique_ptr<AppInfo> FromData(
+ const string& application, const string& version, const string& data,
+ const string& item_uri, std::vector<std::unique_ptr<Item>>* items);
+
+ // Returns the deserialized AppInfo; null if parsing fails.
+ static std::unique_ptr<AppInfo> FromDeserializer(
+ const xml::Deserializer& parent_deserializer,
+ const string& namespace_str);
+
+ // Getters.
+ const string& GetApplication() const;
+ const string& GetVersion() const;
+ const string& GetItemUri() const;
+
+ // Disallow copying.
+ AppInfo(const AppInfo&) = delete;
+ void operator=(const AppInfo&) = delete;
+
+ private:
+ AppInfo();
+
+ bool ParseFields(const xml::Deserializer& deserializer);
+
+ // Required.
+ string application_;
+
+ // At least one of version or item_uri must be present.
+ string version_;
+ string item_uri_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_APP_INFO_H_ // NOLINT
diff --git a/includes/dynamic_depth/camera.h b/includes/dynamic_depth/camera.h
new file mode 100644
index 0000000..9c9c001
--- /dev/null
+++ b/includes/dynamic_depth/camera.h
@@ -0,0 +1,126 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_CAMERA_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_CAMERA_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "dynamic_depth/app_info.h"
+#include "dynamic_depth/depth_map.h"
+#include "dynamic_depth/element.h"
+#include "dynamic_depth/image.h"
+#include "dynamic_depth/imaging_model.h"
+#include "dynamic_depth/light_estimate.h"
+#include "dynamic_depth/point_cloud.h"
+#include "dynamic_depth/pose.h"
+#include "dynamic_depth/vendor_info.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// The camera trait is serialized only if it is one of PHYSICAL or LOGICAL.
+// NONE signifies an undefined trait.
+enum CameraTrait { NONE = 0, PHYSICAL = 1, LOGICAL = 2 };
+
+struct CameraParams {
+ // The Image must be present.
+ std::unique_ptr<Image> image;
+
+ // Optional elements.
+ std::unique_ptr<DepthMap> depth_map;
+ std::unique_ptr<LightEstimate> light_estimate;
+ std::unique_ptr<Pose> pose;
+ std::unique_ptr<ImagingModel> imaging_model;
+ std::unique_ptr<PointCloud> point_cloud;
+ std::unique_ptr<VendorInfo> vendor_info;
+ std::unique_ptr<AppInfo> app_info;
+ CameraTrait trait;
+
+ explicit CameraParams(std::unique_ptr<Image> img)
+ : depth_map(nullptr),
+ light_estimate(nullptr),
+ pose(nullptr),
+ imaging_model(nullptr),
+ point_cloud(nullptr),
+ vendor_info(nullptr),
+ app_info(nullptr),
+ trait(CameraTrait::PHYSICAL) {
+ image = std::move(img);
+ }
+
+ inline bool operator==(const CameraParams& other) const {
+ return image.get() == other.image.get() &&
+ light_estimate.get() == other.light_estimate.get() &&
+ pose.get() == other.pose.get() &&
+ depth_map.get() == other.depth_map.get() &&
+ imaging_model.get() == other.imaging_model.get() &&
+ point_cloud.get() == other.point_cloud.get() &&
+ vendor_info.get() == other.vendor_info.get() &&
+ app_info.get() == other.app_info.get();
+ }
+
+ inline bool operator!=(const CameraParams& other) const {
+ return !(*this == other);
+ }
+};
+
+// Implements the Camera element from the Dynamic Depth specification, with
+// serialization and deserialization.
+class Camera : public Element {
+ public:
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates a Camera from the given objects in params.
+ // Aside from the Image element, all other elements are optional and can be
+ // null.
+ static std::unique_ptr<Camera> FromData(std::unique_ptr<CameraParams> params);
+
+ // Same as above, but allows the Image element to be null. This should be used
+ // only for Camera 0, since the lack of an Image element indicates that it
+ // refers to the JPEG container image. An Item element is generated for the
+ // container image, and populated into items.
+ // Currently supports only image/jpeg.
+ // The items parameter may be null if params->image is not null.
+ // TODO(miraleung): Add other mime types as args when needed.
+ static std::unique_ptr<Camera> FromDataForCamera0(
+ std::unique_ptr<CameraParams> params,
+ std::vector<std::unique_ptr<Item>>* items);
+
+ // Returns the deserialized Camera object, null if parsing fails.
+ // Not sensitive to case when parsing a camera's trait.
+ static std::unique_ptr<Camera> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Getters. Except for Imaeg (which should never be null), these will return
+ // null if the corresponding fields are not present.
+ // which should never be null.
+ const Image* GetImage() const;
+ const LightEstimate* GetLightEstimate() const;
+ const Pose* GetPose() const;
+ const DepthMap* GetDepthMap() const;
+ const ImagingModel* GetImagingModel() const;
+ const PointCloud* GetPointCloud() const;
+ const VendorInfo* GetVendorInfo() const;
+ const AppInfo* GetAppInfo() const;
+ CameraTrait GetTrait() const;
+
+ // Disallow copying.
+ Camera(const Camera&) = delete;
+ void operator=(const Camera&) = delete;
+
+ private:
+ explicit Camera(std::unique_ptr<CameraParams> params);
+
+ std::unique_ptr<CameraParams> params_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_CAMERA_H_ // NOLINT
diff --git a/includes/dynamic_depth/cameras.h b/includes/dynamic_depth/cameras.h
new file mode 100644
index 0000000..5537e03
--- /dev/null
+++ b/includes/dynamic_depth/cameras.h
@@ -0,0 +1,53 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_CAMERAS_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_CAMERAS_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/camera.h"
+#include "dynamic_depth/element.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// Implements the Device:Cameras field from the Dynamic Depth specification,
+// with serialization and deserialization for its child Camera elements.
+class Cameras : public Element {
+ public:
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates this object from the given cameras. Returns null if the list is
+ // empty.
+ // If creation succeeds, ownership of the Camera objects are transferred to
+ // the resulting Cameras object. The vector of Camera objects will be cleared.
+ static std::unique_ptr<Cameras> FromCameraArray(
+ std::vector<std::unique_ptr<Camera>>* camera_list);
+
+ // Returns the deserialized cameras in a Cameras object, null if parsing
+ // failed for all the cameras.
+ static std::unique_ptr<Cameras> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Returns the list of cameras.
+ const std::vector<const Camera*> GetCameras() const;
+
+ // Disallow copying.
+ Cameras(const Cameras&) = delete;
+ void operator=(const Cameras&) = delete;
+
+ private:
+ Cameras();
+
+ std::vector<std::unique_ptr<Camera>> camera_list_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_CAMERAS_H_ // NOLINT
diff --git a/includes/dynamic_depth/container.h b/includes/dynamic_depth/container.h
new file mode 100644
index 0000000..a8a437d
--- /dev/null
+++ b/includes/dynamic_depth/container.h
@@ -0,0 +1,52 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_CONTAINER_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_CONTAINER_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/element.h"
+#include "dynamic_depth/item.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// A Container that holds a directory / array of file Item elementss. Files
+// at the end of the image appear in the same sequential order as the Item
+// elements held in an instance of this object.
+class Container : public Element {
+ public:
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates this object from the given items. Returns null if the list is
+ // empty. If creation succeeds, ownership of the Item objects are transferred
+ // to the resulting Container object. The vector of Item objects will be
+ // cleared.
+ static std::unique_ptr<Container> FromItems(
+ std::vector<std::unique_ptr<Item>>* items);
+
+ // Returns the deserialized item elements, null if parsing failed for all
+ // items.
+ static std::unique_ptr<Container> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Returns the list of cameras.
+ const std::vector<const Item*> GetItems() const;
+
+ // Disallow copying.
+ Container(const Container&) = delete;
+ void operator=(const Container&) = delete;
+
+ private:
+ Container();
+
+ std::vector<std::unique_ptr<Item>> items_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_CONTAINER_H_ // NOLINT
diff --git a/includes/dynamic_depth/depth_map.h b/includes/dynamic_depth/depth_map.h
new file mode 100644
index 0000000..836ff23
--- /dev/null
+++ b/includes/dynamic_depth/depth_map.h
@@ -0,0 +1,128 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_DEPTH_MAP_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_DEPTH_MAP_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "dynamic_depth/element.h"
+#include "dynamic_depth/item.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// The depth conversion format. Please see the Depth Map element in the
+// Dynamnic Depth specification for more details.
+enum class DepthFormat { kFormatNone = 0, kRangeInverse = 1, kRangeLinear = 2 };
+
+// The Units of the depth map. Please see the Depth Map element in the Dynamic
+// Depth specification.
+enum class DepthUnits { kUnitsNone = 0, kMeters = 1, kDiopters = 2 };
+
+// The type of depth measurement. Please see the Depth Map element in the
+// Dynamic Depth specification.
+enum class DepthMeasureType { kOpticalAxis = 1, kOpticRay = 2 };
+
+// The semantics of this depth map.
+enum class DepthItemSemantic { kDepth = 1, kSegmentation = 2 };
+
+struct DepthMapParams {
+ // Mandatory values.
+ DepthFormat format;
+ float near;
+ float far;
+ DepthUnits units;
+ string depth_uri;
+ string mime;
+ DepthItemSemantic item_semantic = DepthItemSemantic::kDepth;
+
+ // The bytes of the depth image. Must be non-empty at write-time (i.e.
+ // programmatic construction).
+ string depth_image_data = "";
+
+ // Optional values.
+ DepthMeasureType measure_type = DepthMeasureType::kOpticalAxis;
+ string confidence_uri = "";
+ // The bytes of the confidence map. If confidence_uri is not empty, the
+ // confidence data must be non-empty at write-time (i.e. programmatic
+ // construction).
+ string confidence_data = "";
+ string software = "";
+
+ // A list of (distance, radius) pairs. This should generally have a short
+ // length, so copying is expected to be inexpensive.
+ std::vector<float> focal_table;
+
+ explicit DepthMapParams(DepthFormat in_format, float in_near, float in_far,
+ DepthUnits in_units, string in_depth_uri)
+ : format(in_format),
+ near(in_near),
+ far(in_far),
+ units(in_units),
+ depth_uri(in_depth_uri) {}
+
+ inline bool operator==(const DepthMapParams& other) const {
+ return format == other.format && near == other.near && far == other.far &&
+ units == other.units && depth_uri == other.depth_uri &&
+ depth_image_data == other.depth_image_data &&
+ measure_type == other.measure_type &&
+ confidence_uri == other.confidence_uri &&
+ confidence_data == other.confidence_data &&
+ software == other.software && focal_table == other.focal_table;
+ }
+
+ inline bool operator!=(const DepthMapParams& other) const {
+ return !(*this == other);
+ }
+};
+
+// Implements the Depth Map element from the Dynamic Depth specification, with
+// serialization and deserialization.
+class DepthMap : public Element {
+ public:
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates a DepthMap from the given objects in params.
+ static std::unique_ptr<DepthMap> FromData(
+ const DepthMapParams& params, std::vector<std::unique_ptr<Item>>* items);
+
+ // Returns the deserialized DepthMap object, null if parsing fails.
+ // Not sensitive to case when parsing the Format, Units, or MeasureType
+ // fields.
+ static std::unique_ptr<DepthMap> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ DepthFormat GetFormat() const;
+ float GetNear() const;
+ float GetFar() const;
+ DepthUnits GetUnits() const;
+ const string GetDepthUri() const;
+ DepthItemSemantic GetItemSemantic() const;
+ const string GetConfidenceUri() const;
+ DepthMeasureType GetMeasureType() const;
+ const string GetSoftware() const;
+ const std::vector<float>& GetFocalTable() const;
+ size_t GetFocalTableEntryCount() const;
+
+ // Disallow copying
+ DepthMap(const DepthMap&) = delete;
+ void operator=(const DepthMap&) = delete;
+
+ private:
+ explicit DepthMap(const DepthMapParams& params);
+ static std::unique_ptr<DepthMap> ParseFields(
+ const xml::Deserializer& deserializer);
+
+ DepthMapParams params_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_DEPTH_MAP_H_ // NOLINT
diff --git a/includes/dynamic_depth/device.h b/includes/dynamic_depth/device.h
new file mode 100644
index 0000000..77f1665
--- /dev/null
+++ b/includes/dynamic_depth/device.h
@@ -0,0 +1,131 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_DEVICE_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_DEVICE_H_ // NOLINT
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/xpath.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "dynamic_depth/app_info.h"
+#include "dynamic_depth/cameras.h"
+#include "dynamic_depth/container.h"
+#include "dynamic_depth/earth_pose.h"
+#include "dynamic_depth/planes.h"
+#include "dynamic_depth/pose.h"
+#include "dynamic_depth/profiles.h"
+#include "dynamic_depth/vendor_info.h"
+#include "xmpmeta/xmp_data.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+struct DeviceParams {
+ // Cameras must be present (i.e. contain at least one camera).
+ std::unique_ptr<Cameras> cameras;
+
+ // GContainer. Optional, depending on element presence or user choice.
+ std::unique_ptr<Container> container;
+
+ // Optional elements.
+ std::unique_ptr<Profiles> profiles;
+ std::unique_ptr<Planes> planes;
+ std::unique_ptr<EarthPose> earth_pose;
+ std::unique_ptr<Pose> pose;
+ std::unique_ptr<VendorInfo> vendor_info;
+ std::unique_ptr<AppInfo> app_info;
+
+ explicit DeviceParams(std::unique_ptr<Cameras> new_cameras)
+ : container(nullptr),
+ profiles(nullptr),
+ planes(nullptr),
+ earth_pose(nullptr),
+ pose(nullptr),
+ vendor_info(nullptr),
+ app_info(nullptr) {
+ cameras = std::move(new_cameras);
+ }
+
+ inline bool operator==(const DeviceParams& other) const {
+ return cameras.get() == other.cameras.get() &&
+ container.get() == other.container.get() &&
+ profiles.get() == other.profiles.get() &&
+ planes.get() == other.planes.get() &&
+ earth_pose.get() == other.earth_pose.get() &&
+ pose.get() == other.pose.get() &&
+ vendor_info.get() == other.vendor_info.get() &&
+ app_info.get() == other.app_info.get();
+ }
+
+ inline bool operator!=(const DeviceParams& other) const {
+ return !(*this == other);
+ }
+};
+
+// Implements a Device from the Dynamic Depth specification, with serialization
+// and deserialization.
+// Does not implement the Element interface because Device is at the top level
+// in the tree.
+class Device {
+ public:
+ // Creates a Device from the given elements.
+ static std::unique_ptr<Device> FromData(std::unique_ptr<DeviceParams> params);
+
+ // Creates a Device from pre-extracted XMP metadata. Returns null if
+ // parsing fails. Both the standard and extended XMP sections are required.
+ static std::unique_ptr<Device> FromXmp(const XmpData& xmp);
+
+ // Creates a Device by extracting XMP metadata from a JPEG and parsing it.
+ // If using XMP for other things as well, FromXmp() should be used instead to
+ // prevent redundant extraction of XMP from the JPEG.
+ static std::unique_ptr<Device> FromJpegFile(const string& filename);
+
+ // Creates a Device by parsing XML file containing the metadata.
+ static std::unique_ptr<Device> FromXmlFile(const string& filename);
+
+ // Getters.
+ // May return null values for optional fields.
+ const Cameras* GetCameras() const;
+ const Container* GetContainer() const;
+ const EarthPose* GetEarthPose() const;
+ const Pose* GetPose() const;
+ const Planes* GetPlanes() const;
+ const Profiles* GetProfiles() const;
+ const VendorInfo* GetVendorInfo() const;
+ const AppInfo* GetAppInfo() const;
+
+ // Not const for XML memory management reasons. More info in source comments.
+ bool SerializeToXmp(XmpData* xmp);
+
+ // Saves Device metadata to a .xml file.
+ bool SerializeToXmlFile(const char* filename);
+
+ // Disallow copying.
+ Device(const Device&) = delete;
+ void operator=(const Device&) = delete;
+
+ private:
+ explicit Device(std::unique_ptr<DeviceParams> params);
+
+ // Retrieves the namespaces of all child elements.
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) const;
+ // Gathers all the XML namespaces of child elements.
+ void PopulateNamespaces();
+ bool Serialize(xmlDocPtr* xmlDoc);
+
+ // Keep a reference to the XML namespaces, so that they are created only once
+ // when Device is constructed.
+ std::unordered_map<string, xmlNsPtr> namespaces_;
+
+ std::unique_ptr<DeviceParams> params_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_DEVICE_H_ // NOLINT
diff --git a/includes/dynamic_depth/dynamic_depth.h b/includes/dynamic_depth/dynamic_depth.h
new file mode 100644
index 0000000..40a0db7
--- /dev/null
+++ b/includes/dynamic_depth/dynamic_depth.h
@@ -0,0 +1,34 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_DYNAMIC_DEPTH_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_DYNAMIC_DEPTH_H_ // NOLINT
+
+#include "dynamic_depth/device.h"
+#include "xmpmeta/xmp_writer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// Serialize a JPEG image, its Dynamic Depth metadata, and GContainer files
+// if applicable.
+bool WriteImageAndMetadataAndContainer(const string& out_filename,
+ const uint8_t* primary_image_bytes,
+ size_t primary_image_bytes_count,
+ Device* device);
+
+// Retrieves the contents of a Container:Item's associated file. The contents
+// are populated into out_payload.
+// As per the Dynamic Depth spec, file contents are base64-encoded if they're
+// of an image/ mime type, and not if they're of a text/ mime type. Dynamic
+// Depth elements that use Container:Item will haev handled this appropriately
+// on item construction.
+bool GetItemPayload(const string& input_image_filename,
+ const Container* container, const string& item_uri,
+ string* out_payload);
+
+// Convenience method for the aboove.
+bool GetItemPayload(const string& input_image_filename, const Device* device,
+ const string& item_uri, string* out_payload);
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_DYNAMIC_DEPTH_H_ // NOLINT
diff --git a/includes/dynamic_depth/earth_pose.h b/includes/dynamic_depth/earth_pose.h
new file mode 100644
index 0000000..0487cdb
--- /dev/null
+++ b/includes/dynamic_depth/earth_pose.h
@@ -0,0 +1,94 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_EARTH_POSE_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_EARTH_POSE_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "dynamic_depth/element.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// Implements the EarthPose element in the Dynamic Depth specification, with
+// serialization and deserialization.
+class EarthPose : public Element {
+ public:
+ // 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: ("EarthPose", "http://ns.google.com/photos/dd/1.0/earthpose/")
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ // Serializes this object. Returns true on success.
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates a EarthPose from the given data.
+ // The order of values in position is latitude, longitude, altitude..
+ // The order of values in orientation is the quaternion x, y, z, w fields.
+ // rotation angle in quaternion format.
+ // Position and orientation are in raw coordinates, and will be stored as
+ // normalied values. Please refer to the Dynamic Depth specification for the
+ // Realm coordinate system.
+ // At least one valid position or orientation must be provided. These
+ // arguments will be ignored if the vector is of the wrong size.
+ static std::unique_ptr<EarthPose> FromData(
+ const std::vector<double>& position,
+ const std::vector<float>& orientation, const int64 timestamp = -1);
+
+ // Returns the deserialized XdmAudio; null if parsing fails.
+ // The returned pointer is owned by the caller.
+ static std::unique_ptr<EarthPose> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Returns true if the device's position is provided.
+ bool HasPosition() const;
+
+ // Returns true if the device's orientation is provided.
+ bool HasOrientation() const;
+
+ // Returns the device's position fields, or an empty vector if they are
+ // not present.
+ const std::vector<double>& GetPosition() const;
+
+ // Returns the device's orientation fields, or an empty vector if they are
+ // not present.
+ const std::vector<float>& GetOrientation() const;
+
+ // Timestamp.
+ int64 GetTimestamp() const;
+
+ // Disallow copying.
+ EarthPose(const EarthPose&) = delete;
+ void operator=(const EarthPose&) = delete;
+
+ private:
+ EarthPose();
+
+ // Extracts device pose fields.
+ bool ParseEarthPoseFields(const xml::Deserializer& deserializer);
+
+ // Position variables, in meters relative to camera 0.
+ // If providing position data, all three fields must be set.
+ // Stored in normalized form.
+ // TODO(miraleung): Cleanup: consider std::optional for this and orientation_.
+ std::vector<double> position_;
+
+ // Orientation variables.
+ // If providing orientation data, all four fields must be set.
+ // Stored in normalized form.
+ std::vector<float> orientation_;
+
+ // Timestamp is Epoch time in milliseconds.
+ int64 timestamp_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_EARTH_POSE_H_ // NOLINT
diff --git a/includes/dynamic_depth/image.h b/includes/dynamic_depth/image.h
new file mode 100644
index 0000000..b944337
--- /dev/null
+++ b/includes/dynamic_depth/image.h
@@ -0,0 +1,89 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_IMAGE_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_IMAGE_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/element.h"
+#include "dynamic_depth/item.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// The ItemSemantic of this Image.
+enum class ImageItemSemantic { kPrimary = 1, kOriginal = 2 };
+
+/**
+ * An Image element for a Dynamic Depth device.
+ */
+class Image : public Element {
+ public:
+ // 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/")
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ // Serializes this object.
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates an original (non-primary) Image from the given fields. Returns null
+ // if one of the following is true:
+ // - Mime field is empty.
+ // - Data field is null or empty.
+ // - Uri field is empty.
+ // - The items field is null.
+ // Param description:
+ // - Data is NOT base64-encoded. This method takes care of encoding when it
+ // is passed to the generated Item element.
+ // - Mime is the mimetype of the image data.
+ // - Uri is the case-sensitive Item:DataUri of this Image, and must be
+ // unique amongst all Dynamic Depth elements. This will be the only way
+ // that metadata parsers will be able to retrieve the image.
+ // Both data and mime are used to construct a new Container:Item, which will
+ // be the last one appended to items.
+ // It is the caller's responsibility to use items to construct a Container,
+ // and ensure that it is serialized along with this Image element.
+ static std::unique_ptr<Image> FromData(
+ const string& data, const string& mime, const string& item_uri,
+ std::vector<std::unique_ptr<Item>>* items);
+
+ // Same as above, but more performant because it avoids an extra string copy.
+ static std::unique_ptr<Image> FromData(
+ const uint8_t* data, size_t data_size, const string& mime,
+ const string& item_uri, std::vector<std::unique_ptr<Item>>* items);
+
+ // Image instantiator for the primary (container) image.
+ static std::unique_ptr<Image> FromDataForPrimaryImage(
+ const string& mime, std::vector<std::unique_ptr<Item>>* items);
+
+ // Returns the deserialized Image; null if parsing fails.
+ static std::unique_ptr<Image> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ const string& GetItemUri() const;
+ ImageItemSemantic GetItemSemantic() const;
+
+ // Disallow copying.
+ Image(const Image&) = delete;
+ void operator=(const Image&) = delete;
+
+ private:
+ Image();
+
+ // Extracts image fields.
+ bool ParseImageFields(const xml::Deserializer& deserializer);
+
+ string item_uri_;
+ ImageItemSemantic item_semantic_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_IMAGE_H_ // NOLINT
diff --git a/includes/dynamic_depth/imaging_model.h b/includes/dynamic_depth/imaging_model.h
new file mode 100644
index 0000000..1175a66
--- /dev/null
+++ b/includes/dynamic_depth/imaging_model.h
@@ -0,0 +1,90 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_IMAGING_MODEL_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_IMAGING_MODEL_H_ // NOLINT
+
+#include <memory>
+#include <unordered_map>
+
+#include "dynamic_depth/dimension.h"
+#include "dynamic_depth/element.h"
+#include "dynamic_depth/point.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+struct ImagingModelParams {
+ // Required. The order of numbers is (x, y), in pixels.
+ Point<double> focal_length;
+
+ // Required. The order of numbers is (width, height), in pixels.
+ Dimension image_size;
+
+ // Optional. Set to (0.5, 0.5) if not present.
+ // The order of numbers is (x, y).
+ Point<double> principal_point;
+
+ // Optional.
+ std::vector<float> distortion; // Distortion parameters.
+ double skew;
+ double pixel_aspect_ratio;
+
+ ImagingModelParams(const Point<double>& focal_len,
+ const Dimension& image_size)
+ : focal_length(focal_len),
+ image_size(image_size),
+ principal_point(Point<double>(0.5, 0.5)),
+ distortion(std::vector<float>()),
+ skew(0),
+ pixel_aspect_ratio(1) {}
+
+ inline bool operator==(const ImagingModelParams& other) const {
+ return focal_length == other.focal_length &&
+ image_size == other.image_size &&
+ principal_point == other.principal_point &&
+ distortion == other.distortion && skew == other.skew &&
+ pixel_aspect_ratio == other.pixel_aspect_ratio;
+ }
+
+ inline bool operator!=(const ImagingModelParams& other) const {
+ return !(*this == other);
+ }
+};
+
+class ImagingModel : public Element {
+ public:
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates an ImagingModel from the given params.
+ static std::unique_ptr<ImagingModel> FromData(
+ const ImagingModelParams& params);
+
+ // Returns the deserialized equirect model, null if parsing fails.
+ static std::unique_ptr<ImagingModel> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Getters.
+ Point<double> GetFocalLength() const;
+ Point<double> GetPrincipalPoint() const;
+ Dimension GetImageSize() const;
+ double GetSkew() const;
+ double GetPixelAspectRatio() const;
+ const std::vector<float>& GetDistortion() const;
+ int GetDistortionCount() const;
+
+ // Disallow copying.
+ ImagingModel(const ImagingModel&) = delete;
+ void operator=(const ImagingModel&) = delete;
+
+ private:
+ ImagingModel(const ImagingModelParams& params);
+
+ ImagingModelParams params_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_IMAGING_MODEL_H_ // NOLINT
diff --git a/includes/dynamic_depth/item.h b/includes/dynamic_depth/item.h
new file mode 100644
index 0000000..d1fcd36
--- /dev/null
+++ b/includes/dynamic_depth/item.h
@@ -0,0 +1,89 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_ITEM_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_ITEM_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/dimension.h"
+#include "dynamic_depth/element.h"
+#include "dynamic_depth/point.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+struct ItemParams {
+ // Required fields.
+ string mime; // Must not be empty.
+ unsigned int length; // Must not be zero.
+
+ // Optional.
+ unsigned int padding;
+ string data_uri;
+
+ // Only for final file serialization - not used in XMP metadata I/O.
+ // IMPORTANT: Callers should enforce that this file exists.
+ // TODO(miraleung): This could be stored as char* to optimize performance.
+ string payload_to_serialize;
+
+ ItemParams(const string& in_mime, unsigned int len)
+ : mime(in_mime),
+ length(len),
+ padding(0),
+ data_uri(""),
+ payload_to_serialize("") {}
+ ItemParams(const string& in_mime, unsigned int len, const string& uri)
+ : mime(in_mime),
+ length(len),
+ padding(0),
+ data_uri(uri),
+ payload_to_serialize("") {}
+
+ inline bool operator==(const ItemParams& other) const {
+ return mime == other.mime && length == other.length &&
+ padding == other.padding && data_uri == other.data_uri &&
+ payload_to_serialize == other.payload_to_serialize;
+ }
+
+ inline bool operator!=(const ItemParams& other) const {
+ return !(*this == other);
+ }
+};
+
+class Item : public Element {
+ public:
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ static std::unique_ptr<Item> FromData(const ItemParams& params);
+
+ // Returns the deserialized item elements, null if parsing failed for all
+ // items.
+ static std::unique_ptr<Item> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ const string& GetMime() const;
+ unsigned int GetLength() const;
+ const string& GetDataUri() const;
+ unsigned int GetPadding() const;
+ const string& GetPayloadToSerialize() const;
+
+ // Disallow copying.
+ Item(const Item&) = delete;
+ void operator=(const Item&) = delete;
+
+ private:
+ Item(const ItemParams& params);
+ static std::unique_ptr<Item> FromDataInternal(const ItemParams& params,
+ bool check_filepath);
+
+ ItemParams params_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_ITEM_H_ // NOLINT
diff --git a/includes/dynamic_depth/light_estimate.h b/includes/dynamic_depth/light_estimate.h
new file mode 100644
index 0000000..51cbde2
--- /dev/null
+++ b/includes/dynamic_depth/light_estimate.h
@@ -0,0 +1,68 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_LIGHT_ESTIMATE_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_LIGHT_ESTIMATE_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/element.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// Light estimation parameters for a camera.
+// This is stored as a sibling element to LightEstimate because it may apply to
+// the container image, so the decoupling is required.
+class LightEstimate : public Element {
+ public:
+ // 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: ("LightEstimate", "http://ns.google.com/photos/dd/1.0/image/")
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ // Serializes this object.
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates an LightEstimate from the given field.
+ static std::unique_ptr<LightEstimate> FromData(float pixel_intensity);
+
+ // Takes the first three values from color_correction if the vector length is
+ // greater than 3.
+ // Color correction values should be between 0 and 1 (plus or minus 0.2).
+ static std::unique_ptr<LightEstimate> FromData(
+ float pixel_intensity, const std::vector<float>& color_correction);
+
+ // Returns the deserialized LightEstimate; null if parsing fails.
+ static std::unique_ptr<LightEstimate> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Returns the average pixel internsity.
+ float GetPixelIntensity() const;
+ const std::vector<float>& GetColorCorrection() const;
+
+ // Disallow copying.
+ LightEstimate(const LightEstimate&) = delete;
+ void operator=(const LightEstimate&) = delete;
+
+ private:
+ LightEstimate();
+
+ float pixel_intensity_ = 1.0f;
+
+ // Optional, either all three together or none at all.
+ // A size-3 vector of color correction values, in the order R, G, B.
+ // Values can be approximately between 0 and 1 (plus or minus 0.2).
+ // On reading back image metadata, if only one or two of the values are
+ // present, then the defaults below are used.
+ std::vector<float> color_correction_ = {1.0f, 1.0f, 1.0f};
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_LIGHT_ESTIMATE_H_ // NOLINT
diff --git a/includes/dynamic_depth/plane.h b/includes/dynamic_depth/plane.h
new file mode 100644
index 0000000..bec2289
--- /dev/null
+++ b/includes/dynamic_depth/plane.h
@@ -0,0 +1,93 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PLANE_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PLANE_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/element.h"
+#include "dynamic_depth/pose.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// A Plane element for a Dynamic Depth device.
+// Only horizontal planes are currently supported.
+class Plane : public Element {
+ public:
+ // 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: ("Plane", "http://ns.google.com/photos/dd/1.0/plane/")
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ // Serializes this object.
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Returns the deserialized Plane; null if parsing fails.
+ static std::unique_ptr<Plane> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Creates a Plane from the given fields.
+ // The Pose must be present.
+ // boundary contains the bounding vertices: [x0, z0, x1, z1, ..., xn, zn].
+ // extent_x is the length of the plane on the X axis.
+ // extent_z is the length of the plane on the Z axis.
+ static std::unique_ptr<Plane> FromData(std::unique_ptr<Pose> pose,
+ const std::vector<float>& boundary,
+ const double extent_x,
+ const double extent_z);
+
+ // Getters.
+ const Pose* GetPose() const;
+
+ // Returns the plane's bounding vertices on the XZ plane.
+ const std::vector<float>& GetBoundary() const;
+
+ // Returns the number of vertices in the boundary polygon.
+ int GetBoundaryVertexCount() const;
+
+ // Returns the plane's length along the X axis.
+ const double GetExtentX() const;
+
+ // Returns the plane's length along the Z axis.
+ const double GetExtentZ() const;
+
+ // Disallow copying.
+ Plane(const Plane&) = delete;
+ void operator=(const Plane&) = delete;
+
+ private:
+ Plane();
+
+ // Extracts plane fields.
+ bool ParsePlaneFields(const xml::Deserializer& deserializer);
+
+ // The pose of the center of this plane.
+ std::unique_ptr<Pose> pose_;
+
+ // Contains the plane's bounding vertices: [x0, z0, x1, z1, ..., xn, zn].
+ // Optional field, set to an empty vector if not present.
+ std::vector<float> boundary_;
+
+ // Required field if "Boundary" is present. Optional otherwise and
+ // expected to be zero; set to 0 if not present.
+ int boundary_vertex_count_;
+
+ // The length of the plane on the X axis. A value of -1 represents infinity.
+ // Optional field, set to -1 (infinity) if not present.
+ double extent_x_;
+
+ // The length of the plane on the Z axis. A value of -1 represents infinity.
+ // Optional field, set to -1 (infinity) if not present.
+ double extent_z_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PLANE_H_ // NOLINT
diff --git a/includes/dynamic_depth/planes.h b/includes/dynamic_depth/planes.h
new file mode 100644
index 0000000..c38aad4
--- /dev/null
+++ b/includes/dynamic_depth/planes.h
@@ -0,0 +1,61 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PLANES_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PLANES_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/element.h"
+#include "dynamic_depth/plane.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// THe list of planes in a Dynamic Depth Device t ype.
+class Planes : public Element {
+ public:
+ // Creates this object from the given planes. Returns null if the list is
+ // empty, null, or contains null elements.
+ // If creation succeeds, ownership of the Plane objects are transferred to
+ // the resulting Planes object. The pointer to the vector of Plane objects
+ // will be replaced with the plane_list_, whose contents are not necessarily
+ // defined.
+ static std::unique_ptr<Planes> FromPlaneArray(
+ std::vector<std::unique_ptr<Plane>>* plane_list);
+
+ // Returns the deserialized cameras in a Planes object, null if parsing
+ // failed for all the planes, one of the planes is null, or the list of
+ // planes was empty.
+ static std::unique_ptr<Planes> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Disallow copying.
+ Planes(const Planes&) = delete;
+ Plane& operator=(const Planes&) = delete;
+
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ // Returns false if the list of planes is empty, or serialization fails.
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Returns the number of plane elements in this Plane object.
+ int GetPlaneCount() const;
+
+ // Returns the ith plane, which is guaranteed not to be null because
+ // FromPlaneArray or FromDeserializer would have failed to construct a valid
+ // Planes object.
+ const Plane* GetPlaneAt(int i) const;
+
+ private:
+ Planes();
+
+ std::vector<std::unique_ptr<Plane>> plane_list_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PLANES_H_ // NOLINT
diff --git a/includes/dynamic_depth/point_cloud.h b/includes/dynamic_depth/point_cloud.h
new file mode 100644
index 0000000..3e53744
--- /dev/null
+++ b/includes/dynamic_depth/point_cloud.h
@@ -0,0 +1,61 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_POINT_CLOUD_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_POINT_CLOUD_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/element.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+// Implements the Point Cloud element from the Dynamic Depth specification, with
+// serialization and deserialization.
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+class PointCloud : public Element {
+ public:
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates a Point Cloud from the given fields. Returns null if position is
+ // empty or points.size() is not divisible by 3.
+ // The first two arguments are required fields, the rest are optional.
+ // points is a list of (x, y, z) tuples, so it must have a size that is
+ // evenly divisible by 3.
+ static std::unique_ptr<PointCloud> FromData(const std::vector<float>& points,
+ bool metric);
+
+ // Returns the deserialized PointCloud; null if parsing fails.
+ // The returned pointer is owned by the caller.
+ static std::unique_ptr<PointCloud> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Getters.
+ // Returns the number of (x, y, z) tuples, *not* the length of points_.
+ int GetPointCount() const;
+ const std::vector<float>& GetPoints() const;
+ bool GetMetric() const;
+
+ PointCloud(const PointCloud&) = delete;
+ void operator=(const PointCloud&) = delete;
+
+ private:
+ PointCloud();
+
+ bool ParseFields(const xml::Deserializer& deserializer);
+
+ // Required fields.
+ std::vector<float> points_;
+
+ // Optional fields.
+ bool metric_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_POINT_CLOUD_H_ // NOLINT
diff --git a/includes/dynamic_depth/pose.h b/includes/dynamic_depth/pose.h
new file mode 100644
index 0000000..9bee030
--- /dev/null
+++ b/includes/dynamic_depth/pose.h
@@ -0,0 +1,98 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_POSE_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_POSE_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "dynamic_depth/element.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+/**
+ * Implements the Pose element in the Dynamic Depth specification, with
+ * serialization and deserialization.
+ * See xdm.org.
+ */
+class Pose : public Element {
+ public:
+ // 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: ("Pose", "http://ns.google.com/photos/dd/1.0/pose/")
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ // Serializes this object. Returns true on success.
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates a Pose from the given data.
+ // The order of values in position is x, y, z.
+ // The order of values in orientation is the quaternion x, y, z, w fields.
+ // This is assusmed to be un-normalized.
+ // Position coordinates are relative to Camera 0, and must be 0 for Camera 0.
+ // Position and orientation are in raw coordinates, and will be stored as
+ // normalied values.
+ // At least one valid position or orientation must be provided. These
+ // arguments will be ignored if the vector is of the wrong size.
+ static std::unique_ptr<Pose> FromData(const std::vector<float>& position,
+ const std::vector<float>& orientation,
+ const int64 timestamp = -1);
+
+ // Returns the deserialized XdmAudio; null if parsing fails.
+ // The returned pointer is owned by the caller.
+ static std::unique_ptr<Pose> FromDeserializer(
+ const xml::Deserializer& parent_deserializer,
+ const char* parent_namespace);
+
+ // Returns true if the device's position is provided.
+ bool HasPosition() const;
+
+ // Returns true if the device's orientation is provided.
+ bool HasOrientation() const;
+
+ // Returns the device's position fields, or an empty vector if they are
+ // not present.
+ const std::vector<float>& GetPosition() const;
+
+ // Returns the device's orientation fields, or an empty vector if they are
+ // not present.
+ const std::vector<float>& GetOrientation() const;
+
+ // Timestamp.
+ int64 GetTimestamp() const;
+
+ // Disallow copying.
+ Pose(const Pose&) = delete;
+ void operator=(const Pose&) = delete;
+
+ private:
+ Pose();
+
+ // Extracts camera pose fields.
+ bool ParsePoseFields(const xml::Deserializer& deserializer);
+
+ // Position variables, in meters relative to camera 0.
+ // If providing position data, all three fields must be set.
+ // Stored in normalized form.
+ // TODO(miraleung): Cleanup: consider std::optional for this and orientation_.
+ std::vector<float> position_;
+
+ // Orientation variables.
+ // If providing orientation data, all four fields must be set.
+ // Stored in normalized form.
+ std::vector<float> orientation_;
+
+ // Timestamp is Epoch time in milliseconds.
+ int64 timestamp_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_POSE_H_ // NOLINT
diff --git a/includes/dynamic_depth/profile.h b/includes/dynamic_depth/profile.h
new file mode 100644
index 0000000..b1a311c
--- /dev/null
+++ b/includes/dynamic_depth/profile.h
@@ -0,0 +1,56 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PROFILE_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PROFILE_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/element.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// Implements the Profile element in the Dynamic Depth specification, with
+// serialization and deserialization.
+class Profile : public Element {
+ public:
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates a Profile element from the given fields. Returns null if
+ // the type is empty, or if the camera_indices are shorter than the
+ // specified minimum length for types supported in the Dynamic Depth
+ // specification. Type is case-sensitive. If wrong, this will be treated as an
+ // unsupported type.
+ static std::unique_ptr<Profile> FromData(
+ const string& type, const std::vector<int>& camera_indices);
+
+ // Returns the deserialized Profile, null if parsing fails.
+ static std::unique_ptr<Profile> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Returns the Profile type.
+ const string& GetType() const;
+
+ // Returns the camera indices.
+ const std::vector<int>& GetCameraIndices() const;
+
+ // Disallow copying.
+ Profile(const Profile&) = delete;
+ void operator=(const Profile&) = delete;
+
+ private:
+ Profile(const string& type, const std::vector<int>& camera_indices);
+
+ string type_;
+ std::vector<int> camera_indices_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PROFILE_H_ // NOLINT
diff --git a/includes/dynamic_depth/profiles.h b/includes/dynamic_depth/profiles.h
new file mode 100644
index 0000000..abcf6c1
--- /dev/null
+++ b/includes/dynamic_depth/profiles.h
@@ -0,0 +1,56 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PROFILES_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PROFILES_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/element.h"
+#include "dynamic_depth/profile.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+// Implements the Device:Profiles field from the Dynamic Depth specification,
+// with serialization and deserialization for its child Profile elements.
+class Profiles : public Element {
+ public:
+ // Interface methods.
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Static methods.
+
+ // Creates this object from the given profiles. If the list is empty, returns
+ // a unique_ptr owning nothing.
+ static std::unique_ptr<Profiles> FromProfileArray(
+ std::vector<std::unique_ptr<Profile>>* profile_list);
+
+ // Returns the deserialized profiles in a Profiles object, a unique_ptr owning
+ // nothing if parsing failed for all the profiles.
+ static std::unique_ptr<Profiles> FromDeserializer(
+ const xml::Deserializer& parent_deserializer);
+
+ // Non-static methods.
+
+ // Returns the list of cameras.
+ const std::vector<const Profile*> GetProfiles() const;
+
+ // Disallow copying
+ Profiles(const Profiles&) = delete;
+ void operator=(const Profiles&) = delete;
+
+ private:
+ Profiles() = default;
+
+ std::vector<std::unique_ptr<Profile>> profile_list_;
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_PROFILES_H_ // NOLINT
diff --git a/includes/dynamic_depth/vendor_info.h b/includes/dynamic_depth/vendor_info.h
new file mode 100644
index 0000000..ba8ca48
--- /dev/null
+++ b/includes/dynamic_depth/vendor_info.h
@@ -0,0 +1,74 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_VENDOR_INFO_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_VENDOR_INFO_H_ // NOLINT
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "dynamic_depth/element.h"
+#include "xmpmeta/xml/deserializer.h"
+#include "xmpmeta/xml/serializer.h"
+
+namespace photos_editing_formats {
+namespace dynamic_depth {
+
+/**
+ * A VendorInfo element for a Dynamic Depth device.
+ */
+class VendorInfo : public Element {
+ public:
+ // 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: ("VendorInfo", "http://ns.google.com/photos/dd/1.0/vendorinfo/")
+ void GetNamespaces(
+ std::unordered_map<string, string>* ns_name_href_map) override;
+
+ // Serializes this object.
+ bool Serialize(xml::Serializer* serializer) const override;
+
+ // Creates an VendorInfo from the given fields. Returns null if
+ // any of the required fields is empty (see fields below).
+ // Manufacturer is the manufacturer of the element that created the content.
+ // Model is the model of the element that created the content.
+ // Notes is general comments.
+ static std::unique_ptr<VendorInfo> FromData(const string& manufacturer,
+ const string& model,
+ const string& notes);
+
+ // Returns the deserialized VendorInfo; null if parsing fails.
+ static std::unique_ptr<VendorInfo> FromDeserializer(
+ const xml::Deserializer& parent_deserializer,
+ const string& namespace_str);
+
+ // Returns the Manufacturer.
+ const string& GetManufacturer() const;
+
+ // Returns the Model.
+ const string& GetModel() const;
+
+ // Returns the Notes.
+ const string& GetNotes() const;
+
+ // Disallow copying.
+ VendorInfo(const VendorInfo&) = delete;
+ void operator=(const VendorInfo&) = delete;
+
+ private:
+ VendorInfo();
+
+ bool ParseFields(const xml::Deserializer& deserializer);
+
+ // Required field.
+ string manufacturer_; // The manufacturer.
+
+ // Optional fields.
+ string model_; // The model.
+ string notes_; // The notes.
+};
+
+} // namespace dynamic_depth
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_VENDOR_INFO_H_ // NOLINT
diff --git a/includes/dynamic_depth/version.h b/includes/dynamic_depth/version.h
new file mode 100644
index 0000000..6193b1a
--- /dev/null
+++ b/includes/dynamic_depth/version.h
@@ -0,0 +1,20 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_VERSION_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_VERSION_H_ // NOLINT
+
+#define DYNAMIC_DEPTH_VERSION_MAJOR 1
+#define DYNAMIC_DEPTH_VERSION_MINOR 0
+#define DYNAMIC_DEPTH_VERSION_REVISION 0
+
+// Classic CPP stringifcation; the extra level of indirection allows the
+// preprocessor to expand the macro before being converted to a string.
+#define DYNAMIC_DEPTH_TO_STRING_HELPER(x) #x
+#define DYNAMIC_DEPTH_TO_STRING(x) DYNAMIC_DEPTH_TO_STRING_HELPER(x)
+
+// The xdmlib version as a string; for example "1.9.0".
+#define DYNAMIC_DEPTH_VERSION_STRING \
+ DYNAMIC_DEPTH_TO_STRING(DYNAMIC_DEPTH_VERSION_MAJOR) \
+ "." DYNAMIC_DEPTH_TO_STRING( \
+ DYNAMIC_DEPTH_VERSION_MINOR) "." \
+ DYNAMIC_DEPTH_TO_STRING(DYNAMIC_DEPTH_VERSION_REVISION)
+
+#endif // DYNAMIC_DEPTH_INCLUDES_DYNAMIC_DEPTH_VERSION_H_ // NOLINT
diff --git a/includes/xmpmeta/jpeg_io.h b/includes/xmpmeta/jpeg_io.h
new file mode 100644
index 0000000..aba2b5a
--- /dev/null
+++ b/includes/xmpmeta/jpeg_io.h
@@ -0,0 +1,53 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_XMPMETA_JPEG_IO_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_JPEG_IO_H_ // NOLINT
+
+#include <string>
+#include <vector>
+
+#include "base/port.h"
+
+namespace photos_editing_formats {
+
+// Contains the data for a section in a JPEG file.
+// A JPEG file contains many sections in addition to image data.
+struct Section {
+ // Constructors.
+ Section() = default;
+ explicit Section(const string& buffer);
+
+ // Returns true if the section's marker matches an APP1 marker.
+ bool IsMarkerApp1();
+
+ int marker;
+ bool is_image_section;
+ string data;
+};
+
+struct ParseOptions {
+ // If set to true, keeps only the EXIF and XMP sections (with
+ // marker kApp1) and ignores others. Otherwise, keeps everything including
+ // image data.
+ bool read_meta_only = false;
+
+ // If section_header is set, this boolean controls whether only the 1st
+ // section matching the section_header will be returned. If not set
+ // (the default), all the sections that math the section header will be
+ // returned.
+ bool section_header_return_first = false;
+
+ // A filter that keeps all the sections whose data starts with the
+ // given string. Ignored if empty.
+ string section_header;
+};
+
+// Parses the JPEG image file.
+std::vector<Section> Parse(const ParseOptions& options,
+ std::istream* input_stream);
+
+// Writes JPEG data sections to a file.
+void WriteSections(const std::vector<Section>& sections,
+ std::ostream* output_stream);
+
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_XMPMETA_JPEG_IO_H_ // NOLINT
diff --git a/includes/xmpmeta/md5.h b/includes/xmpmeta/md5.h
new file mode 100644
index 0000000..a3e8a7f
--- /dev/null
+++ b/includes/xmpmeta/md5.h
@@ -0,0 +1,16 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_XMPMETA_MD5_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_MD5_H_ // NOLINT
+
+#include <string>
+
+#include "base/port.h"
+
+namespace photos_editing_formats {
+
+// Returns the MD5 hash of to_hash as a 32-character hex string.
+// Wrapper around OpenSSL to avoid Gyp dependency problems.
+string MD5Hash(const string& to_hash);
+
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_XMPMETA_MD5_H_ // NOLINT
diff --git a/includes/xmpmeta/version.h b/includes/xmpmeta/version.h
new file mode 100644
index 0000000..91198c4
--- /dev/null
+++ b/includes/xmpmeta/version.h
@@ -0,0 +1,26 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_XMPMETA_VERSION_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_VERSION_H_ // NOLINT
+
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_VERSION_MAJOR 1 // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_VERSION_MINOR 0 // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_VERSION_REVISION 0 // NOLINT
+
+// Classic CPP stringifcation; the extra level of indirection allows the
+// preprocessor to expand the macro before being converted to a string.
+#define \
+ PHOTOS_EDITING_FORMATS_DYNAMIC_DEPTH_INCLUDES_XMPMETA_TO_STRING_HELPER( \
+ x) \
+#x
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_TO_STRING(x) \ // NOLINT
+ PHOTOS_EDITING_FORMATS_DYNAMIC_DEPTH_INCLUDES_XMPMETA_TO_STRING_HELPER(x)
+
+// The Dynamic Depth version as a string; for example "1.9.0".
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_VERSION_STRING \ // NOLINT
+ PHOTOS_EDITING_FORMATS_DYNAMIC_DEPTH_INCLUDES_XMPMETA_TO_STRING( \
+ PHOTOS_EDITING_FORMATS_DYNAMIC_DEPTH_INCLUDES_XMPMETA_VERSION_MAJOR) \
+ "." PHOTOS_EDITING_FORMATS_DYNAMIC_DEPTH_INCLUDES_XMPMETA_TO_STRING( \
+ PHOTOS_EDITING_FORMATS_DYNAMIC_DEPTH_INCLUDES_XMPMETA_VERSION_MINOR) \
+ "." PHOTOS_EDITING_FORMATS_DYNAMIC_DEPTH_INCLUDES_XMPMETA_TO_STRING( \
+ PHOTOS_EDITING_FORMATS_DYNAMIC_DEPTH_INCLUDES_XMPMETA_VERSION_REVISION)
+
+#endif // DYNAMIC_DEPTH_INCLUDES_XMPMETA_VERSION_H_ // NOLINT
diff --git a/includes/xmpmeta/xmp_const.h b/includes/xmpmeta/xmp_const.h
new file mode 100644
index 0000000..c32ecd9
--- /dev/null
+++ b/includes/xmpmeta/xmp_const.h
@@ -0,0 +1,30 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_CONST_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_CONST_H_ // NOLINT
+
+namespace photos_editing_formats {
+
+// Constants used in writing XMP metadata.
+struct XmpConst {
+ // XMP namespaces.
+ static const char* Namespace();
+ static const char* NamespacePrefix();
+ static const char* NodeName();
+ static const char* AdobePropName();
+ static const char* AdobePropValue();
+ static const char* NoteNamespace();
+
+ // XMP headers.
+ static const char* Header();
+ static const char* ExtensionHeader();
+ static const char* HasExtensionPrefix();
+ static const char* HasExtension();
+
+ // Sizes.
+ static const int ExtensionHeaderOffset();
+ static const int MaxBufferSize();
+ static const int ExtendedMaxBufferSize();
+};
+
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_CONST_H_ // NOLINT
diff --git a/includes/xmpmeta/xmp_data.h b/includes/xmpmeta/xmp_data.h
new file mode 100644
index 0000000..1e59b1d
--- /dev/null
+++ b/includes/xmpmeta/xmp_data.h
@@ -0,0 +1,34 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_DATA_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_DATA_H_ // NOLINT
+
+#include <libxml/tree.h>
+
+namespace photos_editing_formats {
+
+// XmpData contains the standard, and optionally extended, XMP metadata from a
+// JPEG file. See xmp_parser for reading XmpData from a JPEG or reading
+// attributes from XmpData.
+class XmpData {
+ public:
+ XmpData();
+ ~XmpData();
+
+ // Frees any allocated resources and resets the xmlDocPtrs to null.
+ void Reset();
+
+ // The standard XMP section.
+ const xmlDocPtr StandardSection() const;
+ xmlDocPtr* MutableStandardSection();
+
+ // The extended XMP section.
+ const xmlDocPtr ExtendedSection() const;
+ xmlDocPtr* MutableExtendedSection();
+
+ private:
+ xmlDocPtr xmp_;
+ xmlDocPtr xmp_extended_;
+};
+
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_DATA_H_ // NOLINT
diff --git a/includes/xmpmeta/xmp_parser.h b/includes/xmpmeta/xmp_parser.h
new file mode 100644
index 0000000..e2781ec
--- /dev/null
+++ b/includes/xmpmeta/xmp_parser.h
@@ -0,0 +1,27 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_PARSER_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_PARSER_H_ // NOLINT
+
+#include <fstream>
+#include <string>
+
+#include "base/port.h"
+#include "xmpmeta/xmp_data.h"
+
+namespace photos_editing_formats {
+
+// Populates a XmpData from the header of the JPEG file.
+bool ReadXmpHeader(const string& filename, bool skip_extended,
+ XmpData* xmp_data);
+
+// Populates a XmpData from the header of JPEG file that has already been read
+// into memory.
+bool ReadXmpFromMemory(const string& jpeg_contents, bool skip_extended,
+ XmpData* xmp_data);
+
+// Populates a XmpData from the header of the given stream (stream data is
+// in JPEG format).
+bool ReadXmpHeader(std::istream* input_stream, bool skip_extended,
+ XmpData* xmp_data);
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_PARSER_H_ // NOLINT
diff --git a/includes/xmpmeta/xmp_writer.h b/includes/xmpmeta/xmp_writer.h
new file mode 100644
index 0000000..e2ea8f7
--- /dev/null
+++ b/includes/xmpmeta/xmp_writer.h
@@ -0,0 +1,43 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_WRITER_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_WRITER_H_ // NOLINT
+
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "base/port.h"
+#include "xmpmeta/xmp_data.h"
+
+namespace photos_editing_formats {
+
+// Creates a new XmpData object and initializes the boilerplate for the
+// standard XMP section.
+// The extended section is initialized only if create_extended is true.
+std::unique_ptr<XmpData> CreateXmpData(bool create_extended);
+
+// Writes XMP data to an existing JPEG image file.
+// This is equivalent to writeXMPMeta in geo/lightfield/metadata/XmpUtil.java.
+// If the extended section is not null, this will modify the given XmpData by
+// setting a property in the standard section that links it with the
+// extended section.
+bool WriteLeftEyeAndXmpMeta(const string& left_data, const string& filename,
+ const XmpData& xmp_data);
+
+// Same as above, but allows the caller to manage their own istream and ostream.
+// filename is written to only if metadata serialization is successful.
+// Assumes the caller will take care of opening and closing the
+// output_jpeg_stream, as well as initialization of the input_jpeg_stream.
+bool WriteLeftEyeAndXmpMeta(const string& filename, const XmpData& xmp_data,
+ std::istringstream* input_jpeg_stream,
+ std::ofstream* output_jpeg_stream);
+
+// Updates a JPEG input stream with new XMP data and writes it to an
+// output stream.
+// This is equivalent to writeXMPMeta in geo/lightfield/metadata/XmpUtil.java.
+bool AddXmpMetaToJpegStream(std::istream* input_jpeg_stream,
+ const XmpData& xmp_data,
+ std::ostream* output_jpeg_stream);
+
+} // namespace photos_editing_formats
+
+#endif // DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMP_WRITER_H_ // NOLINT
diff --git a/includes/xmpmeta/xmpmeta.h b/includes/xmpmeta/xmpmeta.h
new file mode 100644
index 0000000..d27a854
--- /dev/null
+++ b/includes/xmpmeta/xmpmeta.h
@@ -0,0 +1,11 @@
+#ifndef DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMPMETA_H_ // NOLINT
+#define DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMPMETA_H_ // NOLINT
+
+#include "xmpmeta/base64.h"
+#include "xmpmeta/md5.h"
+#include "xmpmeta/xmp_const.h"
+#include "xmpmeta/xmp_data.h"
+#include "xmpmeta/xmp_parser.h"
+#include "xmpmeta/xmp_writer.h"
+
+#endif // DYNAMIC_DEPTH_INCLUDES_XMPMETA_XMPMETA_H_ // NOLINT
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,
+ &timestamp_);
+ 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, &timestamp_);
+ 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, &notes_);
+ 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(&section.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(&section.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 = &section.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(&section);
+ 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(&section->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, &sections)) {
+ 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, &sections)) {
+ return false;
+ }
+
+ WriteSections(sections, output_jpeg_stream);
+ return true;
+}
+
+} // namespace photos_editing_formats