diff options
Diffstat (limited to 'internal/dynamic_depth')
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, + ×tamp_); + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/element.h b/internal/dynamic_depth/element.h new file mode 100644 index 0000000..e68829d --- /dev/null +++ b/internal/dynamic_depth/element.h @@ -0,0 +1,34 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_ELEMENT_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_ELEMENT_H_ // NOLINT + +#include <unordered_map> + +#include "xmpmeta/xml/deserializer.h" +#include "xmpmeta/xml/serializer.h" + +namespace photos_editing_formats { +namespace dynamic_depth { + +/** + * An interface for an element in the Dynamic Depth spec. + */ +class Element { + public: + virtual ~Element() {} + + // Appends child elements' namespaces' and their respective hrefs to the + // given collection, and any parent nodes' names to prefix_names. + // Key: Name of the namespace. + // Value: Full namespace URL. + // Example: ("Image", "http://ns.google.com/photos/dd/1.0/image/") + virtual void GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) = 0; + + // Serializes this element. + virtual bool Serialize(xml::Serializer* serializer) const = 0; +}; + +} // namespace dynamic_depth +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_ELEMENT_H_ // NOLINT diff --git a/internal/dynamic_depth/image.cc b/internal/dynamic_depth/image.cc new file mode 100644 index 0000000..811932e --- /dev/null +++ b/internal/dynamic_depth/image.cc @@ -0,0 +1,193 @@ +#include "dynamic_depth/image.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "dynamic_depth/item.h" + +using photos_editing_formats::dynamic_depth::Item; +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +constexpr char kItemUri[] = "ItemURI"; +constexpr char kItemSemantic[] = "ItemSemantic"; + +constexpr char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/image/"; +constexpr char kPrimaryImagePlaceholderItemUri[] = "primary_image"; + +constexpr char kItemSemanticPrimary[] = "Primary"; +constexpr char kItemSemanticOriginal[] = "Original"; +constexpr char kItemSemanticPrimaryLower[] = "primary"; + +string ItemSemanticToString(ImageItemSemantic item_semantic) { + switch (item_semantic) { + case ImageItemSemantic::kPrimary: + return kItemSemanticPrimary; + case ImageItemSemantic::kOriginal: + return kItemSemanticOriginal; + } +} + +ImageItemSemantic StringToItemSemantic(const string& item_semantic_str) { + string item_semantic_str_lower = item_semantic_str; + std::transform(item_semantic_str_lower.begin(), item_semantic_str_lower.end(), + item_semantic_str_lower.begin(), ::tolower); + if (kItemSemanticPrimaryLower == item_semantic_str_lower) { + return ImageItemSemantic::kPrimary; + } + + // Don't fail, default to Original. + return ImageItemSemantic::kOriginal; +} + +} // namespace + +// Private constructor. +Image::Image() {} + +// Public methods. +void Image::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::Image(), kNamespaceHref); +} + +std::unique_ptr<Image> Image::FromData( + const string& data, const string& mime, const string& item_uri, + std::vector<std::unique_ptr<Item>>* items) { + if (data.empty() || mime.empty()) { + LOG(ERROR) << "No image data or mimetype given"; + return nullptr; + } + + if (item_uri.empty()) { + LOG(ERROR) << "Item URI must be provided"; + return nullptr; + } + + if (items == nullptr) { + LOG(ERROR) << "List of items is null"; + return nullptr; + } + + ItemParams item_params(mime, data.size(), item_uri); + item_params.payload_to_serialize = data; + items->emplace_back(Item::FromData(item_params)); + + std::unique_ptr<Image> image(std::unique_ptr<Image>(new Image())); // NOLINT + image->item_uri_ = item_uri; + image->item_semantic_ = ImageItemSemantic::kOriginal; + return image; +} + +std::unique_ptr<Image> Image::FromDataForPrimaryImage( + const string& mime, std::vector<std::unique_ptr<Item>>* items) { + if (mime.empty()) { + LOG(ERROR) << "No mimetype given"; + return nullptr; + } + + if (items == nullptr) { + LOG(ERROR) << "List of items is null"; + return nullptr; + } + + ItemParams item_params(mime, 0, kPrimaryImagePlaceholderItemUri); + items->emplace_back(Item::FromData(item_params)); + + std::unique_ptr<Image> image(std::unique_ptr<Image>(new Image())); // NOLINT + image->item_uri_ = kPrimaryImagePlaceholderItemUri; + image->item_semantic_ = ImageItemSemantic::kPrimary; + return image; +} + +std::unique_ptr<Image> Image::FromData( + const uint8_t* data, size_t data_size, const string& mime, + const string& item_uri, std::vector<std::unique_ptr<Item>>* items) { + if ((data == nullptr || data_size == 0) || mime.empty()) { + LOG(ERROR) << "No image data or mimetype given"; + return nullptr; + } + + if (item_uri.empty()) { + LOG(ERROR) << "Item URI must be provided"; + return nullptr; + } + + if (items == nullptr) { + LOG(ERROR) << "List of items is null"; + return nullptr; + } + + ItemParams item_params(mime, data_size, item_uri); + item_params.payload_to_serialize = + std::string(reinterpret_cast<const char*>(data), data_size); + items->emplace_back(Item::FromData(item_params)); + + std::unique_ptr<Image> image(std::unique_ptr<Image>(new Image())); // NOLINT + image->item_uri_ = item_uri; + image->item_semantic_ = ImageItemSemantic::kOriginal; + return image; +} + +const string& Image::GetItemUri() const { return item_uri_; } +ImageItemSemantic Image::GetItemSemantic() const { return item_semantic_; } + +std::unique_ptr<Image> Image::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Image()), + DynamicDepthConst::Image()); + if (deserializer == nullptr) { + return nullptr; + } + + std::unique_ptr<Image> image(new Image()); + if (!image->ParseImageFields(*deserializer)) { + return nullptr; + } + return image; +} + +bool Image::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (item_uri_.empty()) { + LOG(ERROR) << "Item URI is empty"; + return false; + } + + return serializer->WriteProperty(DynamicDepthConst::Image(), kItemSemantic, + ItemSemanticToString(item_semantic_)) && + serializer->WriteProperty(DynamicDepthConst::Image(), kItemUri, + item_uri_); +} + +// Private methods. +bool Image::ParseImageFields(const Deserializer& deserializer) { + string item_uri; + string item_semantic_str; + if (!deserializer.ParseString(DynamicDepthConst::Image(), kItemSemantic, + &item_semantic_str) || + !deserializer.ParseString(DynamicDepthConst::Image(), kItemUri, + &item_uri)) { + return false; + } + + item_uri_ = item_uri; + item_semantic_ = StringToItemSemantic(item_semantic_str); + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/imaging_model.cc b/internal/dynamic_depth/imaging_model.cc new file mode 100644 index 0000000..717b8d7 --- /dev/null +++ b/internal/dynamic_depth/imaging_model.cc @@ -0,0 +1,222 @@ +#include "dynamic_depth/imaging_model.h" + +#include <math.h> + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "strings/numbers.h" +#include "xmpmeta/base64.h" + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +constexpr char kFocalLengthX[] = "FocalLengthX"; +constexpr char kFocalLengthY[] = "FocalLengthY"; +constexpr char kImageWidth[] = "ImageWidth"; +constexpr char kImageHeight[] = "ImageHeight"; +constexpr char kPrincipalPointX[] = "PrincipalPointX"; +constexpr char kPrincipalPointY[] = "PrincipalPointY"; +constexpr char kSkew[] = "Skew"; +constexpr char kPixelAspectRatio[] = "PixelAspectRatio"; +constexpr char kDistortion[] = "Distortion"; +constexpr char kDistortionCount[] = "DistortionCount"; + +constexpr char kNamespaceHref[] = + "http://ns.google.com/photos/dd/1.0/imagingmodel/"; + +std::unique_ptr<ImagingModel> ParseFields(const Deserializer& deserializer) { + Point<double> focal_length(0, 0); + Dimension image_size(0, 0); + Point<double> principal_point(0.5, 0.5); + double skew = 0; + double pixel_aspect_ratio = 1.0; + const string& prefix = DynamicDepthConst::ImagingModel(); + + if (!deserializer.ParseDouble(prefix, kFocalLengthX, &focal_length.x) || + !deserializer.ParseDouble(prefix, kFocalLengthY, &focal_length.y) || + !deserializer.ParseInt(prefix, kImageWidth, &image_size.width) || + !deserializer.ParseInt(prefix, kImageHeight, &image_size.height)) { + return nullptr; + } + + double temp1; + double temp2; + if (deserializer.ParseDouble(prefix, kPrincipalPointX, &temp1) && + deserializer.ParseDouble(prefix, kPrincipalPointY, &temp2)) { + principal_point.x = temp1; + principal_point.y = temp2; + } + + if (deserializer.ParseDouble(prefix, kSkew, &temp1)) { + skew = temp1; + } + + if (deserializer.ParseDouble(prefix, kPixelAspectRatio, &temp1)) { + pixel_aspect_ratio = temp1; + } + + int distortion_count = 0; + std::vector<float> distortion; + if (deserializer.ParseInt(DynamicDepthConst::ImagingModel(), kDistortionCount, + &distortion_count)) { + if (distortion_count % 2 != 0) { + LOG(ERROR) << "Parsed DistortionCount = " << distortion_count + << " was expected to be even"; + return nullptr; + } + + deserializer.ParseFloatArrayBase64(DynamicDepthConst::ImagingModel(), + kDistortion, &distortion); + if (distortion.size() != distortion_count * 2) { + LOG(ERROR) << "Parsed DistortionCount of " << distortion_count + << " but should be " << distortion.size() + << " when multiplied by 2"; + return nullptr; + } + } + + ImagingModelParams params(focal_length, image_size); + params.principal_point = principal_point; + params.distortion = distortion; + params.skew = skew; + params.pixel_aspect_ratio = pixel_aspect_ratio; + return ImagingModel::FromData(params); +} + +} // namespace + +// Private constructor. +ImagingModel::ImagingModel(const ImagingModelParams& params) + : params_(params) {} + +// Public methods. +void ImagingModel::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + + ns_name_href_map->emplace(DynamicDepthConst::ImagingModel(), kNamespaceHref); +} + +std::unique_ptr<ImagingModel> ImagingModel::FromData( + const ImagingModelParams& params) { + if (!params.distortion.empty() && params.distortion.size() % 2 != 0) { + LOG(ERROR) << "Distortion must be empty or contain pairs of values, but an " + << " odd number (size=" << params.distortion.size() + << ") was found"; + return nullptr; + } + + return std::unique_ptr<ImagingModel>(new ImagingModel(params)); // NOLINT +} + +std::unique_ptr<ImagingModel> ImagingModel::FromDeserializer( + const xml::Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::ImagingModel()), + DynamicDepthConst::ImagingModel()); + if (deserializer == nullptr) { + LOG(ERROR) << "Deserializer must not be null"; + return nullptr; + } + + return ParseFields(*deserializer); +} + +Point<double> ImagingModel::GetFocalLength() const { + return params_.focal_length; +} + +Point<double> ImagingModel::GetPrincipalPoint() const { + return params_.principal_point; +} + +Dimension ImagingModel::GetImageSize() const { return params_.image_size; } + +double ImagingModel::GetSkew() const { return params_.skew; } + +double ImagingModel::GetPixelAspectRatio() const { + return params_.pixel_aspect_ratio; +} + +const std::vector<float>& ImagingModel::GetDistortion() const { + return params_.distortion; +} + +int ImagingModel::GetDistortionCount() const { + return static_cast<int>(floor(params_.distortion.size() / 2)); +} + +bool ImagingModel::Serialize(xml::Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + // Short-circuiting ensures unnecessary writes will not be performed. + if (!serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kFocalLengthX, + std::to_string(params_.focal_length.x)) || + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kFocalLengthY, + std::to_string(params_.focal_length.y)) || + // Image dimensions. + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), kImageWidth, + std::to_string(params_.image_size.width)) || + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kImageHeight, + std::to_string(params_.image_size.height)) || + // Principal point. + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kPrincipalPointX, + std::to_string(params_.principal_point.x)) || + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kPrincipalPointY, + std::to_string(params_.principal_point.y)) || + // Skew, pixel aspect ratio. + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), kSkew, + std::to_string(params_.skew)) || + !serializer->WriteProperty(DynamicDepthConst::ImagingModel(), + kPixelAspectRatio, + std::to_string(params_.pixel_aspect_ratio))) { + return false; + } + + // Write distortion model only if needed. + if (params_.distortion.empty()) { + return true; + } + + // No error-checking that there are an even number of values in + // params_.distortion, because this is already done in the instantiator and + // deserializer. + string base64_encoded_distortion; + if (!EncodeFloatArrayBase64(params_.distortion, &base64_encoded_distortion)) { + LOG(ERROR) << "Distortion encoding failed"; + return false; + } + + int distortion_count = static_cast<int>(floor(params_.distortion.size() / 2)); + if (!serializer->WriteProperty( + DynamicDepthConst::ImagingModel(), kDistortionCount, + ::dynamic_depth::strings::SimpleItoa(distortion_count))) { + return false; + } + + if (!serializer->WriteProperty(DynamicDepthConst::ImagingModel(), kDistortion, + base64_encoded_distortion)) { + return false; + } + + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/item.cc b/internal/dynamic_depth/item.cc new file mode 100644 index 0000000..a1c2a8f --- /dev/null +++ b/internal/dynamic_depth/item.cc @@ -0,0 +1,139 @@ +#include "dynamic_depth/item.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +constexpr char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/item/"; + +constexpr char kMime[] = "Mime"; +constexpr char kLength[] = "Length"; +constexpr char kPadding[] = "Padding"; +constexpr char kDataUri[] = "DataURI"; + +} // namespace + +// Private constructor. +Item::Item(const ItemParams& params) : params_(params) {} + +// Private instantiator. + +std::unique_ptr<Item> Item::FromDataInternal(const ItemParams& params, + bool check_filepath) { + if (check_filepath && params.length != params.payload_to_serialize.size()) { + LOG(ERROR) << "Length does not match payload's size"; + return nullptr; + } + + if (params.mime.empty()) { + LOG(ERROR) << "Mime is empty"; + return nullptr; + } + + if (params.length < 0) { + LOG(ERROR) << "Item length must be non-negative"; + return nullptr; + } + + if (params.padding > 0 && + static_cast<int>(params.length - params.padding) <= 0) { + LOG(ERROR) << "Item length must be larger than padding; found padding=" + << params.padding << ", length=" << params.length; + return nullptr; + } + + // TODO(miraleung): Check for only supported mime types? + + return std::unique_ptr<Item>(new Item(params)); // NOLINT +} + +void Item::GetNamespaces(std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + + ns_name_href_map->emplace(DynamicDepthConst::Item(), kNamespaceHref); +} + +std::unique_ptr<Item> Item::FromData(const ItemParams& params) { + return FromDataInternal(params, true); +} + +std::unique_ptr<Item> Item::FromDeserializer( + const xml::Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Item()), + DynamicDepthConst::Item()); + if (deserializer == nullptr) { + LOG(ERROR) << "Deserializer must not be null"; + return nullptr; + } + + string mime; + int length; + int padding = 0; + string data_uri; + + if (!deserializer->ParseString(DynamicDepthConst::Item(), kMime, &mime) || + !deserializer->ParseInt(DynamicDepthConst::Item(), kLength, &length)) { + return nullptr; + } + + deserializer->ParseInt(DynamicDepthConst::Item(), kPadding, &padding); + deserializer->ParseString(DynamicDepthConst::Item(), kDataUri, &data_uri); + + ItemParams params(mime, length); + if (!data_uri.empty()) { + params.data_uri = data_uri; + } + if (padding > 0) { + params.padding = padding; + } + + return Item::FromDataInternal(params, false); +} + +// Getters. +const string& Item::GetMime() const { return params_.mime; } +unsigned int Item::GetLength() const { return params_.length; } +const string& Item::GetDataUri() const { return params_.data_uri; } +unsigned int Item::GetPadding() const { return params_.padding; } +const string& Item::GetPayloadToSerialize() const { + return params_.payload_to_serialize; +} + +bool Item::Serialize(xml::Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + // No error-checking for the mime or length here, since it's assumed to be + // taken care of in the instantiator. + bool success = serializer->WriteProperty(DynamicDepthConst::Item(), kMime, + params_.mime) && + serializer->WriteProperty(DynamicDepthConst::Item(), kLength, + std::to_string(params_.length)); + if (!params_.data_uri.empty()) { + success &= serializer->WriteProperty(DynamicDepthConst::Item(), kDataUri, + params_.data_uri); + } + + if (params_.padding > 0) { + success &= serializer->WriteProperty(DynamicDepthConst::Item(), kPadding, + std::to_string(params_.padding)); + } + + return success; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/light_estimate.cc b/internal/dynamic_depth/light_estimate.cc new file mode 100644 index 0000000..9ce3bab --- /dev/null +++ b/internal/dynamic_depth/light_estimate.cc @@ -0,0 +1,128 @@ +#include "dynamic_depth/light_estimate.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "xmpmeta/base64.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { +constexpr int kColorCorrectionSize = 3; + +constexpr char kPixelIntensity[] = "PixelIntensity"; +constexpr char kColorCorrectionR[] = "ColorCorrectionR"; +constexpr char kColorCorrectionG[] = "ColorCorrectionG"; +constexpr char kColorCorrectionB[] = "ColorCorrectionB"; + +constexpr char kNamespaceHref[] = + "http://ns.google.com/photos/dd/1.0/lightestimate/"; + +} // namespace + +// Private constructor. +LightEstimate::LightEstimate() {} + +// Public methods. +void LightEstimate::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::LightEstimate(), kNamespaceHref); +} + +std::unique_ptr<LightEstimate> LightEstimate::FromData(float pixel_intensity) { + return LightEstimate::FromData(pixel_intensity, {1.0f, 1.0f, 1.0f}); +} + +std::unique_ptr<LightEstimate> LightEstimate::FromData( + float pixel_intensity, const std::vector<float>& color_correction) { + // Priate constructor. + std::unique_ptr<LightEstimate> light_estimate( + std::unique_ptr<LightEstimate>(new LightEstimate())); // NOLINT + light_estimate->pixel_intensity_ = pixel_intensity; + + if (color_correction.size() >= kColorCorrectionSize) { + std::copy(color_correction.begin(), + color_correction.begin() + kColorCorrectionSize, + light_estimate->color_correction_.begin()); + } else { + LOG(WARNING) << "Color correction had fewer than three values, " + << "reverting to default of 1.0 for all RGB values"; + } + + return light_estimate; +} + +float LightEstimate::GetPixelIntensity() const { return pixel_intensity_; } + +const std::vector<float>& LightEstimate::GetColorCorrection() const { + return color_correction_; +} + +std::unique_ptr<LightEstimate> LightEstimate::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::LightEstimate()), + DynamicDepthConst::LightEstimate()); + if (deserializer == nullptr) { + return nullptr; + } + + std::unique_ptr<LightEstimate> light_estimate( + std::unique_ptr<LightEstimate>(new LightEstimate())); // NOLINT + if (!deserializer->ParseFloat(DynamicDepthConst::LightEstimate(), + kPixelIntensity, + &light_estimate->pixel_intensity_)) { + return nullptr; + } + + float color_correction_r; + float color_correction_g; + float color_correction_b; + if (deserializer->ParseFloat(DynamicDepthConst::LightEstimate(), + kColorCorrectionR, &color_correction_r) && + deserializer->ParseFloat(DynamicDepthConst::LightEstimate(), + kColorCorrectionG, &color_correction_g) && + deserializer->ParseFloat(DynamicDepthConst::LightEstimate(), + kColorCorrectionB, &color_correction_b)) { + light_estimate->color_correction_[0] = color_correction_r; + light_estimate->color_correction_[1] = color_correction_g; + light_estimate->color_correction_[2] = color_correction_b; + } + + return light_estimate; +} + +bool LightEstimate::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (!serializer->WriteProperty(DynamicDepthConst::LightEstimate(), + kPixelIntensity, + std::to_string(pixel_intensity_))) { + return false; + } + + CHECK(color_correction_.size() >= 3) + << "Color correction not initialized to a size-3 vector"; + return serializer->WriteProperty(DynamicDepthConst::LightEstimate(), + kColorCorrectionR, + std::to_string(color_correction_[0])) && + serializer->WriteProperty(DynamicDepthConst::LightEstimate(), + kColorCorrectionG, + std::to_string(color_correction_[1])) && + serializer->WriteProperty(DynamicDepthConst::LightEstimate(), + kColorCorrectionB, + std::to_string(color_correction_[2])); +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/plane.cc b/internal/dynamic_depth/plane.cc new file mode 100644 index 0000000..7e3914e --- /dev/null +++ b/internal/dynamic_depth/plane.cc @@ -0,0 +1,177 @@ +#include "dynamic_depth/plane.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "strings/numbers.h" +#include "xmpmeta/base64.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +constexpr char kBoundary[] = "Boundary"; +constexpr char kBoundaryVertexCount[] = "BoundaryVertexCount"; +constexpr char kExtentX[] = "ExtentX"; +constexpr char kExtentZ[] = "ExtentZ"; + +constexpr char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/plane/"; + +} // namespace + +// Private constructor. +Plane::Plane() {} + +// Public methods. +void Plane::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::Plane(), kNamespaceHref); + + if (pose_ != nullptr) { + pose_->GetNamespaces(ns_name_href_map); + } +} + +std::unique_ptr<Plane> Plane::FromData(std::unique_ptr<Pose> pose, + const std::vector<float>& boundary, + const double extent_x, + const double extent_z) { + if (pose == nullptr) { + LOG(ERROR) << "The Plane's pose must be provided"; + return nullptr; + } + + if (boundary.size() % 2 != 0) { + LOG(ERROR) << "Number of vertices in the boundary polygon must be 2-tuples"; + return nullptr; + } + + std::unique_ptr<Plane> plane(std::unique_ptr<Plane>(new Plane())); // NOLINT + plane->pose_ = std::move(pose); + plane->boundary_vertex_count_ = boundary.size() / 2; + if (!boundary.empty()) { + plane->boundary_ = boundary; + } + + plane->extent_x_ = extent_x; + plane->extent_z_ = extent_z; + return plane; +} + +std::unique_ptr<Plane> Plane::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Plane()), + DynamicDepthConst::Plane()); + if (deserializer == nullptr) { + LOG(ERROR) << "Deserializer must not be null"; + return nullptr; + } + + std::unique_ptr<Plane> plane(std::unique_ptr<Plane>(new Plane())); // NOLINT + if (!plane->ParsePlaneFields(*deserializer)) { + return nullptr; + } + + return plane; +} + +const Pose* Plane::GetPose() const { return pose_.get(); } + +const std::vector<float>& Plane::GetBoundary() const { return boundary_; } + +int Plane::GetBoundaryVertexCount() const { return boundary_vertex_count_; } + +const double Plane::GetExtentX() const { return extent_x_; } + +const double Plane::GetExtentZ() const { return extent_z_; } + +bool Plane::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (pose_ == nullptr) { + LOG(ERROR) << "Plane's pose must be present, not serializing"; + return false; + } + + if (!serializer->WriteProperty( + DynamicDepthConst::Plane(), kBoundaryVertexCount, + ::dynamic_depth::strings::SimpleItoa(boundary_vertex_count_))) { + return false; + } + if (!boundary_.empty()) { + string base64_encoded_boundary; + if (!EncodeFloatArrayBase64(boundary_, &base64_encoded_boundary)) { + LOG(ERROR) << "Boundary polygon encoding failed."; + return false; + } + + if (!serializer->WriteProperty(DynamicDepthConst::Plane(), kBoundary, + base64_encoded_boundary)) { + return false; + } + } + + if (!serializer->WriteProperty(DynamicDepthConst::Plane(), kExtentX, + std::to_string(extent_x_))) { + return false; + } + + if (!serializer->WriteProperty(DynamicDepthConst::Plane(), kExtentZ, + std::to_string(extent_z_))) { + return false; + } + + std::unique_ptr<Serializer> pose_serializer = serializer->CreateSerializer( + DynamicDepthConst::Plane(), DynamicDepthConst::Pose()); + return pose_->Serialize(pose_serializer.get()); +} + +// Private methods. +bool Plane::ParsePlaneFields(const Deserializer& deserializer) { + std::unique_ptr<Pose> pose = + Pose::FromDeserializer(deserializer, DynamicDepthConst::Plane()); + if (pose == nullptr) { + LOG(ERROR) << "Plane's pose could not be parsed, stopping deserialization"; + return false; + } + + // The BoundaryVertexCount field is required only if the Boundary field is + // populated. + std::vector<float> boundary; + int boundary_vertex_count = 0; + if (deserializer.ParseFloatArrayBase64(DynamicDepthConst::Plane(), kBoundary, + &boundary)) { + if (!deserializer.ParseInt(DynamicDepthConst::Plane(), kBoundaryVertexCount, + &boundary_vertex_count)) { + return false; + } + } + + double extent_x = -1; + deserializer.ParseDouble(DynamicDepthConst::Plane(), kExtentX, &extent_x); + + double extent_z = -1; + deserializer.ParseDouble(DynamicDepthConst::Plane(), kExtentZ, &extent_z); + + pose_ = std::move(pose); + boundary_ = boundary; + boundary_vertex_count_ = boundary_vertex_count; + extent_x_ = extent_x; + extent_z_ = extent_z; + + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/planes.cc b/internal/dynamic_depth/planes.cc new file mode 100644 index 0000000..5eb767f --- /dev/null +++ b/internal/dynamic_depth/planes.cc @@ -0,0 +1,116 @@ +#include "dynamic_depth/planes.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +namespace photos_editing_formats { +namespace dynamic_depth { + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +// Private constructor. +Planes::Planes() = default; + +void Planes::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr || plane_list_.empty()) { + LOG(ERROR) << "Namespace list is null or plane list is empty"; + return; + } + for (const auto& plane : plane_list_) { + plane->GetNamespaces(ns_name_href_map); + } +} + +std::unique_ptr<Planes> Planes::FromPlaneArray( + std::vector<std::unique_ptr<Plane>>* plane_list) { + if (plane_list == nullptr || plane_list->empty()) { + LOG(ERROR) << "Plane list is empty"; + return nullptr; + } + + for (const auto& plane : *plane_list) { + if (!plane) { + LOG(ERROR) << "plane_list cannot contain null elements"; + return nullptr; + } + } + + // Using `new` to access a non-public constructor. + std::unique_ptr<Planes> planes(new Planes()); // NOLINT + std::swap(*plane_list, planes->plane_list_); + return planes; +} + +std::unique_ptr<Planes> Planes::FromDeserializer( + const Deserializer& parent_deserializer) { + // Using `new` to access a non-public constructor. + std::unique_ptr<Planes> planes(new Planes()); // NOLINT + int i = 0; + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(DynamicDepthConst::Planes()), + DynamicDepthConst::Planes(), 0); + while (deserializer) { + std::unique_ptr<Plane> plane = Plane::FromDeserializer(*deserializer); + if (plane == nullptr) { + LOG(ERROR) << "Unable to deserialize a plane"; + return nullptr; + } + + planes->plane_list_.push_back(std::move(plane)); + i++; + deserializer = parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(DynamicDepthConst::Planes()), + DynamicDepthConst::Planes(), i); + } + + if (planes->plane_list_.empty()) { + return nullptr; + } + return planes; +} + +int Planes::GetPlaneCount() const { return plane_list_.size(); } + +const Plane* Planes::GetPlaneAt(int i) const { return plane_list_[i].get(); } + +bool Planes::Serialize(Serializer* serializer) const { + if (plane_list_.empty()) { + LOG(ERROR) << "Plane list is empty"; + return false; + } + std::unique_ptr<Serializer> planes_serializer = + serializer->CreateListSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Planes()), + DynamicDepthConst::Planes()); + if (planes_serializer == nullptr) { + // Error is logged in Serializer. + return false; + } + for (int i = 0; i < plane_list_.size(); i++) { + std::unique_ptr<Serializer> plane_serializer = + planes_serializer->CreateItemSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Plane()), + DynamicDepthConst::Plane()); + if (plane_serializer == nullptr) { + LOG(ERROR) << "Could not create a list item serializer for Plane"; + return false; + } + + if (plane_list_[i] == nullptr) { + LOG(ERROR) << "Plane " << i << " is null, could not serialize"; + continue; + } + + if (!plane_list_[i]->Serialize(plane_serializer.get())) { + LOG(ERROR) << "Could not serialize plane " << i; + continue; + } + } + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/point.h b/internal/dynamic_depth/point.h new file mode 100644 index 0000000..c6eecbb --- /dev/null +++ b/internal/dynamic_depth/point.h @@ -0,0 +1,25 @@ +#ifndef DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_POINT_H_ // NOLINT +#define DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_POINT_H_ // NOLINT + +namespace photos_editing_formats { +namespace dynamic_depth { + +// A struct that contains the width and height of a size or the x and y +// coordinates of a point. +template <typename T> +struct Point { + Point(T x_coord, T y_coord) : x(x_coord), y(y_coord) {} + T x; + T y; + + inline bool operator==(const Point& other) const { + return x == other.x && y == other.y; + } + + inline bool operator!=(const Point& other) const { return !(*this == other); } +}; + +} // namespace dynamic_depth +} // namespace photos_editing_formats + +#endif // DYNAMIC_DEPTH_INTERNAL_DYNAMIC_DEPTH_POINT_H_ // NOLINT diff --git a/internal/dynamic_depth/point_cloud.cc b/internal/dynamic_depth/point_cloud.cc new file mode 100644 index 0000000..f35e9c1 --- /dev/null +++ b/internal/dynamic_depth/point_cloud.cc @@ -0,0 +1,164 @@ +#include "dynamic_depth/point_cloud.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "strings/numbers.h" +#include "xmpmeta/base64.h" +#include "xmpmeta/xml/utils.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kPropertyPrefix[] = "PointCloud"; +const char kPointCount[] = "PointCount"; +const char kPoints[] = "Points"; +const char kMetric[] = "Metric"; + +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/pointcloud/"; + +} // namespace + +// Private constructor. +PointCloud::PointCloud() : metric_(false) {} + +// Public methods. +void PointCloud::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->insert( + std::pair<string, string>(kPropertyPrefix, kNamespaceHref)); +} + +std::unique_ptr<PointCloud> PointCloud::FromData( + const std::vector<float>& points, bool metric) { + if (points.empty()) { + LOG(ERROR) << "No point data given"; + return nullptr; + } + + if (points.size() % 3 != 0) { + LOG(ERROR) << "Points must be (x, y, z) tuples, so the size must be " + << "divisible by 3, got " << points.size(); + return nullptr; + } + + std::unique_ptr<PointCloud> point_cloud(new PointCloud()); + point_cloud->points_ = points; + point_cloud->metric_ = metric; + return point_cloud; +} + +std::unique_ptr<PointCloud> PointCloud::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(kPropertyPrefix), kPropertyPrefix); + if (deserializer == nullptr) { + return nullptr; + } + + std::unique_ptr<PointCloud> point_cloud(new PointCloud()); + if (!point_cloud->ParseFields(*deserializer)) { + return nullptr; + } + return point_cloud; +} + +int PointCloud::GetPointCount() const { + return static_cast<int>(points_.size() / 3); +} + +const std::vector<float>& PointCloud::GetPoints() const { return points_; } +bool PointCloud::GetMetric() const { return metric_; } + +bool PointCloud::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (points_.empty()) { + LOG(ERROR) << "No points in the PointCloud to serialize"; + return false; + } + + // No error checking (e.g. points_.size() % 3 == 0), because serialization + // shouldn't be blocked by this. + string base64_encoded_points; + if (!EncodeFloatArrayBase64(points_, &base64_encoded_points)) { + LOG(ERROR) << "Points encoding failed"; + return false; + } + + // Write required fields. + int point_count = static_cast<int>(points_.size() / 3); + if (!serializer->WriteProperty( + DynamicDepthConst::PointCloud(), kPointCount, + ::dynamic_depth::strings::SimpleItoa(point_count))) { + return false; + } + + if (!serializer->WriteProperty(DynamicDepthConst::PointCloud(), kPoints, + base64_encoded_points)) { + return false; + } + + // Write optional fields. + serializer->WriteBoolProperty(DynamicDepthConst::PointCloud(), kMetric, + metric_); + return true; +} + +// Private methods. +bool PointCloud::ParseFields(const Deserializer& deserializer) { + // Required fields. + std::vector<float> points; + if (!deserializer.ParseFloatArrayBase64(DynamicDepthConst::PointCloud(), + kPoints, &points)) { + return false; + } + + int point_count; + if (!deserializer.ParseInt(DynamicDepthConst::PointCloud(), kPointCount, + &point_count)) { + return false; + } + + if (points.size() % 3 != 0) { + LOG(ERROR) << "Parsed " << points.size() << " values but expected the size " + << "to be divisible by 3 for (x, y, z) tuple representation"; + return false; + } + + int parsed_points_count = static_cast<int>(points.size() / 3); + if (parsed_points_count != point_count) { + LOG(ERROR) << "Parsed PointCount = " << point_count << " but " + << parsed_points_count << " points were found"; + return false; + } + + // We know that point_count == points.size() now. + points_ = points; + + // Optional fields. + bool metric; + if (!deserializer.ParseBoolean(DynamicDepthConst::PointCloud(), kMetric, + &metric)) { + // Set it to the default value. + metric_ = false; + } else { + metric_ = metric; + } + + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/pose.cc b/internal/dynamic_depth/pose.cc new file mode 100644 index 0000000..7e02588 --- /dev/null +++ b/internal/dynamic_depth/pose.cc @@ -0,0 +1,176 @@ +#include "dynamic_depth/pose.h" + +#include <math.h> + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kPositionX[] = "PositionX"; +const char kPositionY[] = "PositionY"; +const char kPositionZ[] = "PositionZ"; +const char kRotationX[] = "RotationX"; +const char kRotationY[] = "RotationY"; +const char kRotationZ[] = "RotationZ"; +const char kRotationW[] = "RotationW"; +const char kTimestamp[] = "Timestamp"; +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/pose/"; + +const std::vector<float> NormalizeQuaternion(const std::vector<float>& quat) { + if (quat.size() < 4) { + return std::vector<float>(); + } + float length = sqrt((quat[0] * quat[0]) + (quat[1] * quat[1]) + + (quat[2] * quat[2]) + (quat[3] * quat[3])); + const std::vector<float> normalized = {quat[0] / length, quat[1] / length, + quat[2] / length, quat[3] / length}; + return normalized; +} + +} // namespace + +// Private constructor. +Pose::Pose() : timestamp_(-1) {} + +// Public methods. +void Pose::GetNamespaces(std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::Pose(), kNamespaceHref); +} + +std::unique_ptr<Pose> Pose::FromData(const std::vector<float>& position, + const std::vector<float>& orientation, + const int64 timestamp) { + if (position.empty() && orientation.empty()) { + LOG(ERROR) << "Either position or orientation must be provided"; + return nullptr; + } + + std::unique_ptr<Pose> pose(new Pose()); + if (position.size() >= 3) { + pose->position_ = position; + } + + if (orientation.size() >= 4) { + pose->orientation_ = NormalizeQuaternion(orientation); + } + + if (timestamp >= 0) { + pose->timestamp_ = timestamp; + } + + return pose; +} + +std::unique_ptr<Pose> Pose::FromDeserializer( + const Deserializer& parent_deserializer, const char* parent_namespace) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer(parent_namespace, + DynamicDepthConst::Pose()); + if (deserializer == nullptr) { + return nullptr; + } + std::unique_ptr<Pose> pose(new Pose()); + if (!pose->ParsePoseFields(*deserializer)) { + return nullptr; + } + return pose; +} + +bool Pose::HasPosition() const { return position_.size() == 3; } +bool Pose::HasOrientation() const { return orientation_.size() == 4; } + +const std::vector<float>& Pose::GetPosition() const { return position_; } + +const std::vector<float>& Pose::GetOrientation() const { return orientation_; } + +int64 Pose::GetTimestamp() const { return timestamp_; } + +bool Pose::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + if (!HasPosition() && !HasOrientation()) { + LOG(ERROR) << "Camera pose has neither position nor orientation"; + return false; + } + + bool success = true; + if (position_.size() == 3) { + success &= serializer->WriteProperty(DynamicDepthConst::Pose(), kPositionX, + std::to_string(position_[0])) && + serializer->WriteProperty(DynamicDepthConst::Pose(), kPositionY, + std::to_string(position_[1])) && + serializer->WriteProperty(DynamicDepthConst::Pose(), kPositionZ, + std::to_string(position_[2])); + } + + if (orientation_.size() == 4) { + success &= serializer->WriteProperty(DynamicDepthConst::Pose(), kRotationX, + std::to_string(orientation_[0])) && + serializer->WriteProperty(DynamicDepthConst::Pose(), kRotationY, + std::to_string(orientation_[1])) && + serializer->WriteProperty(DynamicDepthConst::Pose(), kRotationZ, + std::to_string(orientation_[2])) && + serializer->WriteProperty(DynamicDepthConst::Pose(), kRotationW, + std::to_string(orientation_[3])); + } + + if (timestamp_ >= 0) { + serializer->WriteProperty(DynamicDepthConst::Pose(), kTimestamp, + std::to_string(timestamp_)); + } + + return success; +} + +// Private methods. +bool Pose::ParsePoseFields(const Deserializer& deserializer) { + float x, y, z, w; + const string& prefix = DynamicDepthConst::Pose(); + // If a position field is present, the rest must be as well. + if (deserializer.ParseFloat(prefix, kPositionX, &x)) { + if (!deserializer.ParseFloat(prefix, kPositionY, &y)) { + return false; + } + if (!deserializer.ParseFloat(prefix, kPositionZ, &z)) { + return false; + } + position_ = {x, y, z}; + } + + // Same for orientation. + if (deserializer.ParseFloat(prefix, kRotationX, &x)) { + if (!deserializer.ParseFloat(prefix, kRotationY, &y)) { + return false; + } + if (!deserializer.ParseFloat(prefix, kRotationZ, &z)) { + return false; + } + if (!deserializer.ParseFloat(prefix, kRotationW, &w)) { + return false; + } + orientation_ = std::vector<float>({x, y, z, w}); + } + + if (position_.size() < 3 && orientation_.size() < 4) { + return false; + } + + deserializer.ParseLong(prefix, kTimestamp, ×tamp_); + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/profile.cc b/internal/dynamic_depth/profile.cc new file mode 100644 index 0000000..079f9c2 --- /dev/null +++ b/internal/dynamic_depth/profile.cc @@ -0,0 +1,138 @@ +#include "dynamic_depth/profile.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kType[] = "Type"; +const char kCameraIndices[] = "CameraIndices"; + +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/profile/"; + +// Profile type names. +constexpr char kArPhoto[] = "ARPhoto"; +constexpr char kArPhotoLowercase[] = "arphoto"; +constexpr char kDepthPhoto[] = "DepthPhoto"; +constexpr char kDepthPhotoLowercase[] = "depthphoto"; + +// Profile camera indices' sizes. +constexpr size_t kArPhotoIndicesSize = 1; +constexpr size_t kDepthPhotoIndicesSize = 1; + +// Returns true if the type is unsupported, or if the type is supported in the +// Dynamic Depth Profile element and the size of the camera indices matches that +// specified in the spec. +bool ValidateKnownTypeAndIndices(const string& type, size_t camera_indices_size, + string* matched_type) { + string type_lower = type; + std::transform(type_lower.begin(), type_lower.end(), type_lower.begin(), + ::tolower); + bool isArPhoto = (kArPhotoLowercase == type_lower); + bool isDepthPhoto = (kDepthPhotoLowercase == type_lower); + if (!isArPhoto && !isDepthPhoto) { + return true; + } + bool matches = + (isArPhoto && camera_indices_size >= kArPhotoIndicesSize) || + (isDepthPhoto && camera_indices_size >= kDepthPhotoIndicesSize); + if (!matches) { + LOG(WARNING) << "Size of camera indices for " + << (isArPhoto ? kArPhoto : kDepthPhoto) << " must be at least " + << (isArPhoto ? kArPhotoIndicesSize : kDepthPhotoIndicesSize); + } else { + *matched_type = isArPhoto ? kArPhoto : kDepthPhoto; + } + + return matches; +} + +} // namespace + +// Private constructor. +Profile::Profile(const string& type, const std::vector<int>& camera_indices) + : type_(type), camera_indices_(camera_indices) {} + +// Public methods. +void Profile::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->emplace(DynamicDepthConst::Profile(), kNamespaceHref); +} + +std::unique_ptr<Profile> Profile::FromData( + const string& type, const std::vector<int>& camera_indices) { + if (type.empty()) { + LOG(ERROR) << "Profile must have a type"; + return nullptr; + } + // Check that the camera indices' length is at least the size of that + // specified for the type. This has no restrictions on unsupported profile + // types. + // Ensure also that a consistent case-sensitive profile is stored, even if the + // caller provided a case-insensitive name. + string matched_type; + if (!ValidateKnownTypeAndIndices(type, camera_indices.size(), + &matched_type)) { + return nullptr; + } + + return std::unique_ptr<Profile>( + new Profile(matched_type.empty() ? type : + matched_type, camera_indices)); // NOLINT +} + +std::unique_ptr<Profile> Profile::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Profile()), + DynamicDepthConst::Profile()); + if (deserializer == nullptr) { + return nullptr; + } + std::unique_ptr<Profile> profile(new Profile("", {})); + if (!deserializer->ParseString(DynamicDepthConst::Profile(), kType, + &profile->type_)) { + return nullptr; + } + deserializer->ParseIntArray(DynamicDepthConst::Profile(), kCameraIndices, + &profile->camera_indices_); + if (!ValidateKnownTypeAndIndices( + profile->type_, profile->camera_indices_.size(), &profile->type_)) { + return nullptr; + } + return profile; +} + +const string& Profile::GetType() const { return type_; } + +const std::vector<int>& Profile::GetCameraIndices() const { + return camera_indices_; +} + +bool Profile::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + if (!serializer->WriteProperty(DynamicDepthConst::Profile(), kType, type_)) { + return false; + } + if (camera_indices_.empty()) { + return true; + } + return serializer->WriteIntArray(DynamicDepthConst::Profile(), kCameraIndices, + camera_indices_); +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/profiles.cc b/internal/dynamic_depth/profiles.cc new file mode 100644 index 0000000..b23b5d7 --- /dev/null +++ b/internal/dynamic_depth/profiles.cc @@ -0,0 +1,99 @@ +#include "dynamic_depth/profiles.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { + +void Profiles::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr || profile_list_.empty()) { + LOG(ERROR) << "Namespace list is null or profile list is empty"; + return; + } + for (const auto& profile : profile_list_) { + profile->GetNamespaces(ns_name_href_map); + } +} + +std::unique_ptr<Profiles> Profiles::FromProfileArray( + std::vector<std::unique_ptr<Profile>>* profile_list) { + if (profile_list->empty()) { + LOG(ERROR) << "Profile list is empty"; + return nullptr; + } + std::unique_ptr<Profiles> profiles(new Profiles()); + profiles->profile_list_ = std::move(*profile_list); + return profiles; +} + +std::unique_ptr<Profiles> Profiles::FromDeserializer( + const Deserializer& parent_deserializer) { + std::unique_ptr<Profiles> profiles(new Profiles()); + int i = 0; + for (std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(DynamicDepthConst::Profiles()), + DynamicDepthConst::Profiles(), i); + deserializer != nullptr; + deserializer = parent_deserializer.CreateDeserializerFromListElementAt( + DynamicDepthConst::Namespace(DynamicDepthConst::Profiles()), + DynamicDepthConst::Profiles(), ++i)) { + std::unique_ptr<Profile> profile = Profile::FromDeserializer(*deserializer); + if (profile != nullptr) { + profiles->profile_list_.emplace_back(std::move(profile)); + } + } + + if (profiles->profile_list_.empty()) { + return nullptr; + } + return profiles; +} + +const std::vector<const Profile*> Profiles::GetProfiles() const { + std::vector<const Profile*> profile_list; + for (const auto& profile : profile_list_) { + profile_list.push_back(profile.get()); + } + return profile_list; +} + +bool Profiles::Serialize(Serializer* serializer) const { + if (profile_list_.empty()) { + LOG(ERROR) << "Profile list is empty"; + return false; + } + bool success = true; + int i = 0; + std::unique_ptr<Serializer> profiles_serializer = + serializer->CreateListSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Profiles()), + DynamicDepthConst::Profiles()); + if (profiles_serializer == nullptr) { + // Error is logged in Serializer. + return false; + } + for (const auto& profile : profile_list_) { + std::unique_ptr<Serializer> profile_serializer = + profiles_serializer->CreateItemSerializer( + DynamicDepthConst::Namespace(DynamicDepthConst::Profile()), + DynamicDepthConst::Profile()); + if (profile_serializer == nullptr) { + continue; + } + success &= profile->Serialize(profile_serializer.get()); + if (!success) { + LOG(ERROR) << "Could not serialize profile " << i; + } + ++i; + } + return success; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats diff --git a/internal/dynamic_depth/vendor_info.cc b/internal/dynamic_depth/vendor_info.cc new file mode 100644 index 0000000..db73840 --- /dev/null +++ b/internal/dynamic_depth/vendor_info.cc @@ -0,0 +1,108 @@ +#include "dynamic_depth/vendor_info.h" + +#include "android-base/logging.h" +#include "dynamic_depth/const.h" +#include "xmpmeta/base64.h" +#include "xmpmeta/xml/utils.h" + +using photos_editing_formats::xml::Deserializer; +using photos_editing_formats::xml::Serializer; + +namespace photos_editing_formats { +namespace dynamic_depth { +namespace { + +const char kPropertyPrefix[] = "VendorInfo"; +const char kModel[] = "Model"; +const char kManufacturer[] = "Manufacturer"; +const char kNotes[] = "Notes"; + +const char kNamespaceHref[] = "http://ns.google.com/photos/dd/1.0/vendorinfo/"; + +} // namespace + +// Private constructor. +VendorInfo::VendorInfo() : manufacturer_(""), model_(""), notes_("") {} + +// Public methods. +void VendorInfo::GetNamespaces( + std::unordered_map<string, string>* ns_name_href_map) { + if (ns_name_href_map == nullptr) { + LOG(ERROR) << "Namespace list or own namespace is null"; + return; + } + ns_name_href_map->insert( + std::pair<string, string>(kPropertyPrefix, kNamespaceHref)); +} + +std::unique_ptr<VendorInfo> VendorInfo::FromData(const string& manufacturer, + const string& model, + const string& notes) { + if (manufacturer.empty()) { + LOG(ERROR) << "No manufacturer data given"; + return nullptr; + } + std::unique_ptr<VendorInfo> vendor_info(new VendorInfo()); + vendor_info->model_ = model; + vendor_info->manufacturer_ = manufacturer; + vendor_info->notes_ = notes; + return vendor_info; +} + +std::unique_ptr<VendorInfo> VendorInfo::FromDeserializer( + const Deserializer& parent_deserializer, const string& namespace_str) { + std::unique_ptr<Deserializer> deserializer = + parent_deserializer.CreateDeserializer(namespace_str, kPropertyPrefix); + if (deserializer == nullptr) { + return nullptr; + } + + std::unique_ptr<VendorInfo> vendor_info(new VendorInfo()); + if (!vendor_info->ParseFields(*deserializer)) { + return nullptr; + } + return vendor_info; +} + +const string& VendorInfo::GetManufacturer() const { return manufacturer_; } +const string& VendorInfo::GetModel() const { return model_; } +const string& VendorInfo::GetNotes() const { return notes_; } + +bool VendorInfo::Serialize(Serializer* serializer) const { + if (serializer == nullptr) { + LOG(ERROR) << "Serializer is null"; + return false; + } + + // Write required field. + if (!serializer->WriteProperty(DynamicDepthConst::VendorInfo(), kManufacturer, + manufacturer_)) { + return false; + } + + // Write optional fields. + if (!model_.empty()) { + serializer->WriteProperty(DynamicDepthConst::VendorInfo(), kModel, model_); + } + if (!notes_.empty()) { + serializer->WriteProperty(DynamicDepthConst::VendorInfo(), kNotes, notes_); + } + return true; +} + +// Private methods. +bool VendorInfo::ParseFields(const Deserializer& deserializer) { + // Required field. + if (!deserializer.ParseString(DynamicDepthConst::VendorInfo(), kManufacturer, + &manufacturer_)) { + return false; + } + + // Optional fields. + deserializer.ParseString(DynamicDepthConst::VendorInfo(), kModel, &model_); + deserializer.ParseString(DynamicDepthConst::VendorInfo(), kNotes, ¬es_); + return true; +} + +} // namespace dynamic_depth +} // namespace photos_editing_formats |