summaryrefslogtreecommitdiff
path: root/internal/dynamic_depth
diff options
context:
space:
mode:
Diffstat (limited to 'internal/dynamic_depth')
-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
25 files changed, 3559 insertions, 0 deletions
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