Merge branch 'master' into ios-ml-image-utils
This commit is contained in:
		
						commit
						b940a19462
					
				|  | @ -12,8 +12,6 @@ nav_order: 1 | ||||||
| {:toc} | {:toc} | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| ## C++ Graph Builder |  | ||||||
| 
 |  | ||||||
| C++ graph builder is a powerful tool for: | C++ graph builder is a powerful tool for: | ||||||
| 
 | 
 | ||||||
| *   Building complex graphs | *   Building complex graphs | ||||||
|  | @ -25,7 +23,7 @@ C++ graph builder is a powerful tool for: | ||||||
| *   Supporting optional graph inputs/outputs | *   Supporting optional graph inputs/outputs | ||||||
| *   Customizing graphs per platform | *   Customizing graphs per platform | ||||||
| 
 | 
 | ||||||
| ### Basic Usage | ## Basic Usage | ||||||
| 
 | 
 | ||||||
| Let's see how C++ graph builder can be used for a simple graph: | Let's see how C++ graph builder can be used for a simple graph: | ||||||
| 
 | 
 | ||||||
|  | @ -95,9 +93,9 @@ Short summary: | ||||||
|         unleashing graph builder capabilities and improving your graphs |         unleashing graph builder capabilities and improving your graphs | ||||||
|         readability. |         readability. | ||||||
| 
 | 
 | ||||||
| ### Advanced Usage | ## Advanced Usage | ||||||
| 
 | 
 | ||||||
| #### Utility Functions | ### Utility Functions | ||||||
| 
 | 
 | ||||||
| Let's extract inference construction code into a dedicated utility function to | Let's extract inference construction code into a dedicated utility function to | ||||||
| help for readability and code reuse: | help for readability and code reuse: | ||||||
|  | @ -162,7 +160,7 @@ graphs construction code and helps automatically pull in calculator dependencies | ||||||
| (e.g. no need to manually add `:inference_calculator` dep, just let your IDE | (e.g. no need to manually add `:inference_calculator` dep, just let your IDE | ||||||
| include `inference.h` and build cleaner pull in corresponding dependency). | include `inference.h` and build cleaner pull in corresponding dependency). | ||||||
| 
 | 
 | ||||||
| #### Utility Classes | ### Utility Classes | ||||||
| 
 | 
 | ||||||
| And surely, it's not only about functions, in some cases it's beneficial to | And surely, it's not only about functions, in some cases it's beneficial to | ||||||
| introduce utility classes which can help making your graph construction code | introduce utility classes which can help making your graph construction code | ||||||
|  | @ -277,3 +275,69 @@ Tip: the same as for the `RunInference` function, extracting | ||||||
| `PassThroughNodeBuilder` and similar utility classes into dedicated modules | `PassThroughNodeBuilder` and similar utility classes into dedicated modules | ||||||
| enables reuse in graph construction code and helps to automatically pull in the | enables reuse in graph construction code and helps to automatically pull in the | ||||||
| corresponding calculator dependencies. | corresponding calculator dependencies. | ||||||
|  | 
 | ||||||
|  | ## Dos and Don'ts | ||||||
|  | 
 | ||||||
|  | ### Define graph inputs at the very beginning if possible | ||||||
|  | 
 | ||||||
|  | ```c++ {.bad} | ||||||
|  | Stream<D> RunSomething(Stream<A> a, Stream<B> b, Graph& graph) { | ||||||
|  |   Stream<C> c = graph.In(2).SetName("c").Cast<C>();  // Bad. | ||||||
|  |   // ... | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | CalculatorGraphConfig BuildGraph() { | ||||||
|  |   Graph graph; | ||||||
|  | 
 | ||||||
|  |   Stream<A> a = graph.In(0).SetName("a").Cast<A>(); | ||||||
|  |   // 10/100/N lines of code. | ||||||
|  |   Stream<B> b = graph.In(1).SetName("b").Cast<B>()  // Bad. | ||||||
|  |   Stream<D> d = RunSomething(a, b, graph); | ||||||
|  |   // ... | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | In the above code: | ||||||
|  | 
 | ||||||
|  | *   It can be hard to guess how many inputs you have in the graph. | ||||||
|  | *   Can be error prone overall and hard to maintain in future (e.g. is it a | ||||||
|  |     correct index? name? what if some inputs are removed or made optional? | ||||||
|  |     etc.). | ||||||
|  | 
 | ||||||
|  | Instead, simply define your graph inputs at the very beginning of your graph | ||||||
|  | builder: | ||||||
|  | 
 | ||||||
|  | ```c++ {.good} | ||||||
|  | Stream<int> RunSomething(Stream<A> a, Stream<B> b, Stream<C> c, Graph& graph) { | ||||||
|  |   // ... | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | CalculatorGraphConfig BuildGraph() { | ||||||
|  |   Graph graph; | ||||||
|  | 
 | ||||||
|  |   Stream<A> a = graph.In(0).SetName("a").Cast<A>(); | ||||||
|  |   Stream<B> b = graph.In(1).SetName("b").Cast<B>(); | ||||||
|  |   Stream<C> c = graph.In(2).SetName("c").Cast<C>(); | ||||||
|  | 
 | ||||||
|  |   // 10/100/N lines of code. | ||||||
|  |   Stream<D> d = RunSomething(a, b, c, graph); | ||||||
|  |   // ... | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | And if you have an input stream or side packet that is not always defined - | ||||||
|  | simply use `std::optional` and put it at the very beginning as well: | ||||||
|  | 
 | ||||||
|  | ```c++ {.good} | ||||||
|  | std::optional<Stream<A>> a; | ||||||
|  | if (needs_a) { | ||||||
|  |   a = graph.In(0).SetName(a).Cast<A>(); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Note: of course, there can be exceptions - for example, there can be a use case | ||||||
|  | where calling `RunSomething1(..., graph)`, ..., `RunSomethingN(..., graph)` is | ||||||
|  | **intended to add new inputs**, so afterwards you can iterate over them and feed | ||||||
|  | only added inputs into the graph. However, in any case, try to make it easy for | ||||||
|  | readers to find out what graph inputs it has or may have. | ||||||
|  |  | ||||||
|  | @ -489,9 +489,12 @@ cc_test( | ||||||
| 
 | 
 | ||||||
| cc_library( | cc_library( | ||||||
|     name = "frame_buffer", |     name = "frame_buffer", | ||||||
|  |     srcs = ["frame_buffer.cc"], | ||||||
|     hdrs = ["frame_buffer.h"], |     hdrs = ["frame_buffer.h"], | ||||||
|     deps = [ |     deps = [ | ||||||
|         "//mediapipe/framework/port:integral_types", |         "//mediapipe/framework/port:integral_types", | ||||||
|         "@com_google_absl//absl/log:check", |         "@com_google_absl//absl/log:check", | ||||||
|  |         "@com_google_absl//absl/status", | ||||||
|  |         "@com_google_absl//absl/status:statusor", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
|  |  | ||||||
							
								
								
									
										176
									
								
								mediapipe/framework/formats/frame_buffer.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								mediapipe/framework/formats/frame_buffer.cc
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,176 @@ | ||||||
|  | /* Copyright 2023 The MediaPipe Authors. All Rights Reserved.
 | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | ==============================================================================*/ | ||||||
|  | 
 | ||||||
|  | #include "mediapipe/framework/formats/frame_buffer.h" | ||||||
|  | 
 | ||||||
|  | #include "absl/status/status.h" | ||||||
|  | #include "absl/status/statusor.h" | ||||||
|  | 
 | ||||||
|  | namespace mediapipe { | ||||||
|  | 
 | ||||||
|  | namespace { | ||||||
|  | 
 | ||||||
|  | // Returns whether the input `format` is a supported YUV format.
 | ||||||
|  | bool IsSupportedYuvFormat(FrameBuffer::Format format) { | ||||||
|  |   return format == FrameBuffer::Format::kNV21 || | ||||||
|  |          format == FrameBuffer::Format::kNV12 || | ||||||
|  |          format == FrameBuffer::Format::kYV12 || | ||||||
|  |          format == FrameBuffer::Format::kYV21; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Returns supported 1-plane FrameBuffer in YuvData structure.
 | ||||||
|  | absl::StatusOr<FrameBuffer::YuvData> GetYuvDataFromOnePlaneFrameBuffer( | ||||||
|  |     const FrameBuffer& source) { | ||||||
|  |   if (!IsSupportedYuvFormat(source.format())) { | ||||||
|  |     return absl::InvalidArgumentError( | ||||||
|  |         "The source FrameBuffer format is not part of YUV420 family."); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   FrameBuffer::YuvData result; | ||||||
|  |   const int y_buffer_size = | ||||||
|  |       source.plane(0).stride().row_stride_bytes * source.dimension().height; | ||||||
|  |   const int uv_buffer_size = | ||||||
|  |       ((source.plane(0).stride().row_stride_bytes + 1) / 2) * | ||||||
|  |       ((source.dimension().height + 1) / 2); | ||||||
|  |   result.y_buffer = source.plane(0).buffer(); | ||||||
|  |   result.y_row_stride = source.plane(0).stride().row_stride_bytes; | ||||||
|  |   result.uv_row_stride = result.y_row_stride; | ||||||
|  | 
 | ||||||
|  |   if (source.format() == FrameBuffer::Format::kNV21) { | ||||||
|  |     result.v_buffer = result.y_buffer + y_buffer_size; | ||||||
|  |     result.u_buffer = result.v_buffer + 1; | ||||||
|  |     result.uv_pixel_stride = 2; | ||||||
|  |     // If y_row_stride equals to the frame width and is an odd value,
 | ||||||
|  |     // uv_row_stride = y_row_stride + 1, otherwise uv_row_stride = y_row_stride.
 | ||||||
|  |     if (result.y_row_stride == source.dimension().width && | ||||||
|  |         result.y_row_stride % 2 == 1) { | ||||||
|  |       result.uv_row_stride = (result.y_row_stride + 1) / 2 * 2; | ||||||
|  |     } | ||||||
|  |   } else if (source.format() == FrameBuffer::Format::kNV12) { | ||||||
|  |     result.u_buffer = result.y_buffer + y_buffer_size; | ||||||
|  |     result.v_buffer = result.u_buffer + 1; | ||||||
|  |     result.uv_pixel_stride = 2; | ||||||
|  |     // If y_row_stride equals to the frame width and is an odd value,
 | ||||||
|  |     // uv_row_stride = y_row_stride + 1, otherwise uv_row_stride = y_row_stride.
 | ||||||
|  |     if (result.y_row_stride == source.dimension().width && | ||||||
|  |         result.y_row_stride % 2 == 1) { | ||||||
|  |       result.uv_row_stride = (result.y_row_stride + 1) / 2 * 2; | ||||||
|  |     } | ||||||
|  |   } else if (source.format() == FrameBuffer::Format::kYV21) { | ||||||
|  |     result.u_buffer = result.y_buffer + y_buffer_size; | ||||||
|  |     result.v_buffer = result.u_buffer + uv_buffer_size; | ||||||
|  |     result.uv_pixel_stride = 1; | ||||||
|  |     result.uv_row_stride = (result.y_row_stride + 1) / 2; | ||||||
|  |   } else if (source.format() == FrameBuffer::Format::kYV12) { | ||||||
|  |     result.v_buffer = result.y_buffer + y_buffer_size; | ||||||
|  |     result.u_buffer = result.v_buffer + uv_buffer_size; | ||||||
|  |     result.uv_pixel_stride = 1; | ||||||
|  |     result.uv_row_stride = (result.y_row_stride + 1) / 2; | ||||||
|  |   } | ||||||
|  |   return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Returns supported 2-plane FrameBuffer in YuvData structure.
 | ||||||
|  | absl::StatusOr<FrameBuffer::YuvData> GetYuvDataFromTwoPlaneFrameBuffer( | ||||||
|  |     const FrameBuffer& source) { | ||||||
|  |   if (source.format() != FrameBuffer::Format::kNV12 && | ||||||
|  |       source.format() != FrameBuffer::Format::kNV21) { | ||||||
|  |     return absl::InvalidArgumentError("Unsupported YUV planar format."); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   FrameBuffer::YuvData result; | ||||||
|  |   // Y plane
 | ||||||
|  |   result.y_buffer = source.plane(0).buffer(); | ||||||
|  |   // All plane strides
 | ||||||
|  |   result.y_row_stride = source.plane(0).stride().row_stride_bytes; | ||||||
|  |   result.uv_row_stride = source.plane(1).stride().row_stride_bytes; | ||||||
|  |   result.uv_pixel_stride = 2; | ||||||
|  | 
 | ||||||
|  |   if (source.format() == FrameBuffer::Format::kNV12) { | ||||||
|  |     // Y and UV interleaved format
 | ||||||
|  |     result.u_buffer = source.plane(1).buffer(); | ||||||
|  |     result.v_buffer = result.u_buffer + 1; | ||||||
|  |   } else { | ||||||
|  |     // Y and VU interleaved format
 | ||||||
|  |     result.v_buffer = source.plane(1).buffer(); | ||||||
|  |     result.u_buffer = result.v_buffer + 1; | ||||||
|  |   } | ||||||
|  |   return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Returns supported 3-plane FrameBuffer in YuvData structure. Note that NV21
 | ||||||
|  | // and NV12 are included in the supported Yuv formats. Technically, NV21 and
 | ||||||
|  | // NV12 should not be described by the 3-plane format. Historically, NV21 is
 | ||||||
|  | // used loosely such that it can also be used to describe YV21 format. For
 | ||||||
|  | // backwards compatibility, FrameBuffer supports NV21/NV12 with 3-plane format
 | ||||||
|  | // but such usage is discouraged
 | ||||||
|  | absl::StatusOr<FrameBuffer::YuvData> GetYuvDataFromThreePlaneFrameBuffer( | ||||||
|  |     const FrameBuffer& source) { | ||||||
|  |   if (!IsSupportedYuvFormat(source.format())) { | ||||||
|  |     return absl::InvalidArgumentError( | ||||||
|  |         "The source FrameBuffer format is not part of YUV420 family."); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (source.plane(1).stride().row_stride_bytes != | ||||||
|  |           source.plane(2).stride().row_stride_bytes || | ||||||
|  |       source.plane(1).stride().pixel_stride_bytes != | ||||||
|  |           source.plane(2).stride().pixel_stride_bytes) { | ||||||
|  |     return absl::InternalError("Unsupported YUV planar format."); | ||||||
|  |   } | ||||||
|  |   FrameBuffer::YuvData result; | ||||||
|  |   if (source.format() == FrameBuffer::Format::kNV21 || | ||||||
|  |       source.format() == FrameBuffer::Format::kYV12) { | ||||||
|  |     // Y follow by VU order. The VU chroma planes can be interleaved or
 | ||||||
|  |     // planar.
 | ||||||
|  |     result.y_buffer = source.plane(0).buffer(); | ||||||
|  |     result.v_buffer = source.plane(1).buffer(); | ||||||
|  |     result.u_buffer = source.plane(2).buffer(); | ||||||
|  |     result.y_row_stride = source.plane(0).stride().row_stride_bytes; | ||||||
|  |     result.uv_row_stride = source.plane(1).stride().row_stride_bytes; | ||||||
|  |     result.uv_pixel_stride = source.plane(1).stride().pixel_stride_bytes; | ||||||
|  |   } else { | ||||||
|  |     // Y follow by UV order. The UV chroma planes can be interleaved or
 | ||||||
|  |     // planar.
 | ||||||
|  |     result.y_buffer = source.plane(0).buffer(); | ||||||
|  |     result.u_buffer = source.plane(1).buffer(); | ||||||
|  |     result.v_buffer = source.plane(2).buffer(); | ||||||
|  |     result.y_row_stride = source.plane(0).stride().row_stride_bytes; | ||||||
|  |     result.uv_row_stride = source.plane(1).stride().row_stride_bytes; | ||||||
|  |     result.uv_pixel_stride = source.plane(1).stride().pixel_stride_bytes; | ||||||
|  |   } | ||||||
|  |   return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | }  // namespace
 | ||||||
|  | 
 | ||||||
|  | absl::StatusOr<FrameBuffer::YuvData> FrameBuffer::GetYuvDataFromFrameBuffer( | ||||||
|  |     const FrameBuffer& source) { | ||||||
|  |   if (!IsSupportedYuvFormat(source.format())) { | ||||||
|  |     return absl::InvalidArgumentError( | ||||||
|  |         "The source FrameBuffer format is not part of YUV420 family."); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (source.plane_count() == 1) { | ||||||
|  |     return GetYuvDataFromOnePlaneFrameBuffer(source); | ||||||
|  |   } else if (source.plane_count() == 2) { | ||||||
|  |     return GetYuvDataFromTwoPlaneFrameBuffer(source); | ||||||
|  |   } else if (source.plane_count() == 3) { | ||||||
|  |     return GetYuvDataFromThreePlaneFrameBuffer(source); | ||||||
|  |   } | ||||||
|  |   return absl::InvalidArgumentError( | ||||||
|  |       "The source FrameBuffer must be consisted by 1, 2, or 3 planes"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | }  // namespace mediapipe
 | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| /* Copyright 2022 The MediaPipe Authors. All Rights Reserved.
 | /* Copyright 2023 The MediaPipe Authors. All Rights Reserved.
 | ||||||
| 
 | 
 | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
| you may not use this file except in compliance with the License. | you may not use this file except in compliance with the License. | ||||||
|  | @ -19,6 +19,7 @@ limitations under the License. | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| #include "absl/log/check.h" | #include "absl/log/check.h" | ||||||
|  | #include "absl/status/statusor.h" | ||||||
| #include "mediapipe/framework/port/integral_types.h" | #include "mediapipe/framework/port/integral_types.h" | ||||||
| 
 | 
 | ||||||
| namespace mediapipe { | namespace mediapipe { | ||||||
|  | @ -118,6 +119,20 @@ class FrameBuffer { | ||||||
|     int Size() const { return width * height; } |     int Size() const { return width * height; } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   // YUV data structure.
 | ||||||
|  |   struct YuvData { | ||||||
|  |     const uint8* y_buffer; | ||||||
|  |     const uint8* u_buffer; | ||||||
|  |     const uint8* v_buffer; | ||||||
|  |     // Y buffer row stride in bytes.
 | ||||||
|  |     int y_row_stride; | ||||||
|  |     // U/V buffer row stride in bytes.
 | ||||||
|  |     int uv_row_stride; | ||||||
|  |     // U/V pixel stride in bytes. This is the distance between two consecutive
 | ||||||
|  |     // u/v pixel values in a row.
 | ||||||
|  |     int uv_pixel_stride; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   // Builds a FrameBuffer object from a row-major backing buffer.
 |   // Builds a FrameBuffer object from a row-major backing buffer.
 | ||||||
|   //
 |   //
 | ||||||
|   // The FrameBuffer does not take ownership of the backing buffer. The caller
 |   // The FrameBuffer does not take ownership of the backing buffer. The caller
 | ||||||
|  | @ -150,6 +165,12 @@ class FrameBuffer { | ||||||
|   // Returns FrameBuffer format.
 |   // Returns FrameBuffer format.
 | ||||||
|   Format format() const { return format_; } |   Format format() const { return format_; } | ||||||
| 
 | 
 | ||||||
|  |   // Returns YuvData which contains the Y, U, and V buffer and their
 | ||||||
|  |   // stride info from the input `source` FrameBuffer which is in the YUV family
 | ||||||
|  |   // formats (e.g NV12, NV21, YV12, and YV21).
 | ||||||
|  |   static absl::StatusOr<YuvData> GetYuvDataFromFrameBuffer( | ||||||
|  |       const FrameBuffer& source); | ||||||
|  | 
 | ||||||
|  private: |  private: | ||||||
|   std::vector<Plane> planes_; |   std::vector<Plane> planes_; | ||||||
|   Dimension dimension_; |   Dimension dimension_; | ||||||
|  |  | ||||||
|  | @ -87,6 +87,8 @@ cc_library( | ||||||
| cc_library( | cc_library( | ||||||
|     name = "builtin_task_graphs", |     name = "builtin_task_graphs", | ||||||
|     deps = [ |     deps = [ | ||||||
|  |         "//mediapipe/tasks/cc/audio/audio_classifier:audio_classifier_graph", | ||||||
|  |         "//mediapipe/tasks/cc/audio/audio_embedder:audio_embedder_graph", | ||||||
|         "//mediapipe/tasks/cc/vision/gesture_recognizer:gesture_recognizer_graph", |         "//mediapipe/tasks/cc/vision/gesture_recognizer:gesture_recognizer_graph", | ||||||
|         "//mediapipe/tasks/cc/vision/image_classifier:image_classifier_graph", |         "//mediapipe/tasks/cc/vision/image_classifier:image_classifier_graph", | ||||||
|         "//mediapipe/tasks/cc/vision/image_embedder:image_embedder_graph", |         "//mediapipe/tasks/cc/vision/image_embedder:image_embedder_graph", | ||||||
|  | @ -94,11 +96,8 @@ cc_library( | ||||||
|         "//mediapipe/tasks/cc/vision/object_detector:object_detector_graph", |         "//mediapipe/tasks/cc/vision/object_detector:object_detector_graph", | ||||||
|     ] + select({ |     ] + select({ | ||||||
|         # TODO: Build text_classifier_graph and text_embedder_graph on Windows. |         # TODO: Build text_classifier_graph and text_embedder_graph on Windows. | ||||||
|         # TODO: Build audio_classifier_graph and audio_embedder_graph on Windows. |  | ||||||
|         "//mediapipe:windows": [], |         "//mediapipe:windows": [], | ||||||
|         "//conditions:default": [ |         "//conditions:default": [ | ||||||
|             "//mediapipe/tasks/cc/audio/audio_classifier:audio_classifier_graph", |  | ||||||
|             "//mediapipe/tasks/cc/audio/audio_embedder:audio_embedder_graph", |  | ||||||
|             "//mediapipe/tasks/cc/text/text_classifier:text_classifier_graph", |             "//mediapipe/tasks/cc/text/text_classifier:text_classifier_graph", | ||||||
|             "//mediapipe/tasks/cc/text/text_embedder:text_embedder_graph", |             "//mediapipe/tasks/cc/text/text_embedder:text_embedder_graph", | ||||||
|         ], |         ], | ||||||
|  |  | ||||||
							
								
								
									
										58
									
								
								mediapipe/tasks/ios/test/vision/core/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								mediapipe/tasks/ios/test/vision/core/BUILD
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | ||||||
|  | load( | ||||||
|  |     "@build_bazel_rules_apple//apple:ios.bzl", | ||||||
|  |     "ios_unit_test", | ||||||
|  | ) | ||||||
|  | load( | ||||||
|  |     "//mediapipe/tasks:ios/ios.bzl", | ||||||
|  |     "MPP_TASK_MINIMUM_OS_VERSION", | ||||||
|  | ) | ||||||
|  | load( | ||||||
|  |     "@org_tensorflow//tensorflow/lite:special_rules.bzl", | ||||||
|  |     "tflite_ios_lab_runner", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | package(default_visibility = ["//mediapipe/tasks:internal"]) | ||||||
|  | 
 | ||||||
|  | licenses(["notice"]) | ||||||
|  | 
 | ||||||
|  | # Default tags for filtering iOS targets. Targets are restricted to Apple platforms. | ||||||
|  | TFL_DEFAULT_TAGS = [ | ||||||
|  |     "apple", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | # Following sanitizer tests are not supported by iOS test targets. | ||||||
|  | TFL_DISABLED_SANITIZER_TAGS = [ | ||||||
|  |     "noasan", | ||||||
|  |     "nomsan", | ||||||
|  |     "notsan", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | objc_library( | ||||||
|  |     name = "MPPImageObjcTestLibrary", | ||||||
|  |     testonly = 1, | ||||||
|  |     srcs = ["MPPImageTests.m"], | ||||||
|  |     data = [ | ||||||
|  |         "//mediapipe/tasks/testdata/vision:test_images", | ||||||
|  |     ], | ||||||
|  |     sdk_frameworks = [ | ||||||
|  |         "CoreMedia", | ||||||
|  |         "CoreVideo", | ||||||
|  |         "CoreGraphics", | ||||||
|  |         "UIKit", | ||||||
|  |         "Accelerate", | ||||||
|  |     ], | ||||||
|  |     deps = [ | ||||||
|  |         "//mediapipe/tasks/ios/common:MPPCommon", | ||||||
|  |         "//mediapipe/tasks/ios/vision/core:MPPImage", | ||||||
|  |     ], | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | ios_unit_test( | ||||||
|  |     name = "MPPImageObjcTest", | ||||||
|  |     minimum_os_version = MPP_TASK_MINIMUM_OS_VERSION, | ||||||
|  |     runner = tflite_ios_lab_runner("IOS_LATEST"), | ||||||
|  |     tags = TFL_DEFAULT_TAGS + TFL_DISABLED_SANITIZER_TAGS, | ||||||
|  |     deps = [ | ||||||
|  |         ":MPPImageObjcTestLibrary", | ||||||
|  |     ], | ||||||
|  | ) | ||||||
							
								
								
									
										358
									
								
								mediapipe/tasks/ios/test/vision/core/MPPImageTests.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								mediapipe/tasks/ios/test/vision/core/MPPImageTests.m
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,358 @@ | ||||||
|  | // Copyright 2023 The MediaPipe Authors. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //      http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  | 
 | ||||||
|  | #import "mediapipe/tasks/ios/common/sources/MPPCommon.h" | ||||||
|  | #import "mediapipe/tasks/ios/vision/core/sources/MPPImage.h" | ||||||
|  | 
 | ||||||
|  | #import <Accelerate/Accelerate.h> | ||||||
|  | #import <CoreGraphics/CoreGraphics.h> | ||||||
|  | #import <CoreMedia/CoreMedia.h> | ||||||
|  | #import <CoreVideo/CoreVideo.h> | ||||||
|  | #import <XCTest/XCTest.h> | ||||||
|  | #import <UIKit/UIKit.h> | ||||||
|  | 
 | ||||||
|  | NS_ASSUME_NONNULL_BEGIN | ||||||
|  | 
 | ||||||
|  | static NSString *const kTestImageName = @"burger"; | ||||||
|  | static NSString *const kTestImageType = @"jpg"; | ||||||
|  | static CGFloat kTestImageWidthInPixels = 480.0f; | ||||||
|  | static CGFloat kTestImageHeightInPixels = 325.0f; | ||||||
|  | static NSString *const kExpectedErrorDomain = @"com.google.mediapipe.tasks"; | ||||||
|  | 
 | ||||||
|  | #define AssertEqualErrors(error, expectedError)                                               \ | ||||||
|  |   XCTAssertNotNil(error);                                                                     \ | ||||||
|  |   XCTAssertEqualObjects(error.domain, expectedError.domain);                                  \ | ||||||
|  |   XCTAssertEqual(error.code, expectedError.code);                                             \ | ||||||
|  |   XCTAssertNotEqual(                                                                          \ | ||||||
|  |       [error.localizedDescription rangeOfString:expectedError.localizedDescription].location, \ | ||||||
|  |       NSNotFound) | ||||||
|  | 
 | ||||||
|  | /** Unit tests for `MPPImage`. */ | ||||||
|  | @interface MPPImageTests : XCTestCase | ||||||
|  | 
 | ||||||
|  | /** Test image. */ | ||||||
|  | @property(nonatomic, nullable) UIImage *image; | ||||||
|  | 
 | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @implementation MPPImageTests | ||||||
|  | 
 | ||||||
|  | #pragma mark - Tests | ||||||
|  | 
 | ||||||
|  | - (void)setUp { | ||||||
|  |   [super setUp]; | ||||||
|  |   NSString *imageName = [[NSBundle bundleForClass:[self class]] pathForResource:kTestImageName | ||||||
|  |                                                                          ofType:kTestImageType]; | ||||||
|  |   self.image = [[UIImage alloc] initWithContentsOfFile:imageName]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)tearDown { | ||||||
|  |   self.image = nil; | ||||||
|  |   [super tearDown]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)assertMPPImage:(nullable MPPImage *)mppImage | ||||||
|  |          hasSourceType:(MPPImageSourceType)sourceType | ||||||
|  |         hasOrientation:(UIImageOrientation)expectedOrientation | ||||||
|  |                  width:(CGFloat)expectedWidth | ||||||
|  |                 height:(CGFloat)expectedHeight { | ||||||
|  |   XCTAssertNotNil(mppImage); | ||||||
|  |   XCTAssertEqual(mppImage.imageSourceType, sourceType); | ||||||
|  |   XCTAssertEqual(mppImage.orientation, expectedOrientation); | ||||||
|  |   XCTAssertEqualWithAccuracy(mppImage.width, expectedWidth, FLT_EPSILON); | ||||||
|  |   XCTAssertEqualWithAccuracy(mppImage.height, expectedHeight, FLT_EPSILON); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)assertInitFailsWithImage:(nullable MPPImage *)mppImage | ||||||
|  |                            error:(NSError *)error | ||||||
|  |                    expectedError:(NSError *)expectedError { | ||||||
|  |   XCTAssertNil(mppImage); | ||||||
|  |   XCTAssertNotNil(error); | ||||||
|  |   AssertEqualErrors(error, expectedError); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithImageSuceeds { | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithUIImage:self.image error:nil]; | ||||||
|  |   [self assertMPPImage:mppImage | ||||||
|  |          hasSourceType:MPPImageSourceTypeImage | ||||||
|  |         hasOrientation:self.image.imageOrientation | ||||||
|  |                  width:kTestImageWidthInPixels | ||||||
|  |                 height:kTestImageHeightInPixels]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithImageAndOrientation { | ||||||
|  |   UIImageOrientation orientation = UIImageOrientationRight; | ||||||
|  | 
 | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithUIImage:self.image | ||||||
|  |                                              orientation:orientation | ||||||
|  |                                                    error:nil]; | ||||||
|  |   [self assertMPPImage:mppImage | ||||||
|  |          hasSourceType:MPPImageSourceTypeImage | ||||||
|  |         hasOrientation:orientation | ||||||
|  |                  width:kTestImageWidthInPixels | ||||||
|  |                 height:kTestImageHeightInPixels]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithImage_nilImage { | ||||||
|  |   NSError *error; | ||||||
|  | 
 | ||||||
|  | #pragma clang diagnostic push | ||||||
|  | #pragma clang diagnostic ignored "-Wnonnull" | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithUIImage:nil error:&error]; | ||||||
|  | #pragma clang diagnostic pop | ||||||
|  | 
 | ||||||
|  |   [self | ||||||
|  |       assertInitFailsWithImage:mppImage | ||||||
|  |                          error:error | ||||||
|  |                  expectedError:[NSError errorWithDomain:kExpectedErrorDomain | ||||||
|  |                                                    code:MPPTasksErrorCodeInvalidArgumentError | ||||||
|  |                                                userInfo:@{ | ||||||
|  |                                                  NSLocalizedDescriptionKey : @"Image cannot be nil." | ||||||
|  |                                                }]]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithImageAndOrientation_nilImage { | ||||||
|  |   NSError *error; | ||||||
|  | 
 | ||||||
|  | #pragma clang diagnostic push | ||||||
|  | #pragma clang diagnostic ignored "-Wnonnull" | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithUIImage:nil | ||||||
|  |                                              orientation:UIImageOrientationRight | ||||||
|  |                                                    error:&error]; | ||||||
|  | #pragma clang diagnostic pop | ||||||
|  | 
 | ||||||
|  |   [self | ||||||
|  |       assertInitFailsWithImage:mppImage | ||||||
|  |                          error:error | ||||||
|  |                  expectedError:[NSError errorWithDomain:kExpectedErrorDomain | ||||||
|  |                                                    code:MPPTasksErrorCodeInvalidArgumentError | ||||||
|  |                                                userInfo:@{ | ||||||
|  |                                                  NSLocalizedDescriptionKey : @"Image cannot be nil." | ||||||
|  |                                                }]]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithSampleBuffer { | ||||||
|  |   CMSampleBufferRef sampleBuffer = [self sampleBuffer]; | ||||||
|  | 
 | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithSampleBuffer:sampleBuffer error:nil]; | ||||||
|  |   [self assertMPPImage:mppImage | ||||||
|  |          hasSourceType:MPPImageSourceTypeSampleBuffer | ||||||
|  |         hasOrientation:UIImageOrientationUp | ||||||
|  |                  width:kTestImageWidthInPixels | ||||||
|  |                 height:kTestImageHeightInPixels]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithSampleBufferAndOrientation { | ||||||
|  |   UIImageOrientation orientation = UIImageOrientationRight; | ||||||
|  |   CMSampleBufferRef sampleBuffer = [self sampleBuffer]; | ||||||
|  | 
 | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithSampleBuffer:sampleBuffer | ||||||
|  |                                                   orientation:orientation | ||||||
|  |                                                         error:nil]; | ||||||
|  |   [self assertMPPImage:mppImage | ||||||
|  |          hasSourceType:MPPImageSourceTypeSampleBuffer | ||||||
|  |         hasOrientation:orientation | ||||||
|  |                  width:kTestImageWidthInPixels | ||||||
|  |                 height:kTestImageHeightInPixels]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithSampleBuffer_nilImage { | ||||||
|  |   NSError *error; | ||||||
|  | 
 | ||||||
|  | #pragma clang diagnostic push | ||||||
|  | #pragma clang diagnostic ignored "-Wnonnull" | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithSampleBuffer:nil error:&error]; | ||||||
|  | #pragma clang diagnostic pop | ||||||
|  | 
 | ||||||
|  |   [self | ||||||
|  |       assertInitFailsWithImage:mppImage | ||||||
|  |                          error:error | ||||||
|  |                  expectedError: | ||||||
|  |                      [NSError errorWithDomain:kExpectedErrorDomain | ||||||
|  |                                          code:MPPTasksErrorCodeInvalidArgumentError | ||||||
|  |                                      userInfo:@{ | ||||||
|  |                                        NSLocalizedDescriptionKey : | ||||||
|  |                                            @"Sample buffer is not valid. Invoking " | ||||||
|  |                                            @"CMSampleBufferIsValid(sampleBuffer) must return true." | ||||||
|  |                                      }]]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithSampleBufferAndOrientation_nilImage { | ||||||
|  |   NSError *error; | ||||||
|  | 
 | ||||||
|  | #pragma clang diagnostic push | ||||||
|  | #pragma clang diagnostic ignored "-Wnonnull" | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithSampleBuffer:nil | ||||||
|  |                                                   orientation:UIImageOrientationRight | ||||||
|  |                                                         error:&error]; | ||||||
|  | #pragma clang diagnostic pop | ||||||
|  | 
 | ||||||
|  |   [self | ||||||
|  |       assertInitFailsWithImage:mppImage | ||||||
|  |                          error:error | ||||||
|  |                  expectedError: | ||||||
|  |                      [NSError errorWithDomain:kExpectedErrorDomain | ||||||
|  |                                          code:MPPTasksErrorCodeInvalidArgumentError | ||||||
|  |                                      userInfo:@{ | ||||||
|  |                                        NSLocalizedDescriptionKey : | ||||||
|  |                                            @"Sample buffer is not valid. Invoking " | ||||||
|  |                                            @"CMSampleBufferIsValid(sampleBuffer) must return true." | ||||||
|  |                                      }]]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithPixelBuffer { | ||||||
|  |   CMSampleBufferRef sampleBuffer = [self sampleBuffer]; | ||||||
|  |   CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); | ||||||
|  | 
 | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithPixelBuffer:pixelBuffer error:nil]; | ||||||
|  |   [self assertMPPImage:mppImage | ||||||
|  |          hasSourceType:MPPImageSourceTypePixelBuffer | ||||||
|  |         hasOrientation:UIImageOrientationUp | ||||||
|  |                  width:kTestImageWidthInPixels | ||||||
|  |                 height:kTestImageHeightInPixels]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithPixelBufferAndOrientation { | ||||||
|  |   UIImageOrientation orientation = UIImageOrientationRight; | ||||||
|  | 
 | ||||||
|  |   CMSampleBufferRef sampleBuffer = [self sampleBuffer]; | ||||||
|  |   CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); | ||||||
|  | 
 | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithPixelBuffer:pixelBuffer | ||||||
|  |                                                  orientation:orientation | ||||||
|  |                                                        error:nil]; | ||||||
|  |   [self assertMPPImage:mppImage | ||||||
|  |          hasSourceType:MPPImageSourceTypePixelBuffer | ||||||
|  |         hasOrientation:orientation | ||||||
|  |                  width:kTestImageWidthInPixels | ||||||
|  |                 height:kTestImageHeightInPixels]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithPixelBuffer_nilImage { | ||||||
|  |   NSError *error; | ||||||
|  | 
 | ||||||
|  | #pragma clang diagnostic push | ||||||
|  | #pragma clang diagnostic ignored "-Wnonnull" | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithPixelBuffer:nil error:&error]; | ||||||
|  | #pragma clang diagnostic pop | ||||||
|  | 
 | ||||||
|  |   [self assertInitFailsWithImage:mppImage | ||||||
|  |                            error:error | ||||||
|  |                    expectedError:[NSError errorWithDomain:kExpectedErrorDomain | ||||||
|  |                                                      code:MPPTasksErrorCodeInvalidArgumentError | ||||||
|  |                                                  userInfo:@{ | ||||||
|  |                                                    NSLocalizedDescriptionKey : | ||||||
|  |                                                        @"Pixel Buffer cannot be nil." | ||||||
|  |                                                  }]]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)testInitWithPixelBufferAndOrientation_nilImage { | ||||||
|  |   NSError *error; | ||||||
|  | 
 | ||||||
|  | #pragma clang diagnostic push | ||||||
|  | #pragma clang diagnostic ignored "-Wnonnull" | ||||||
|  |   MPPImage *mppImage = [[MPPImage alloc] initWithPixelBuffer:nil | ||||||
|  |                                                  orientation:UIImageOrientationRight | ||||||
|  |                                                        error:&error]; | ||||||
|  | #pragma clang diagnostic pop | ||||||
|  | 
 | ||||||
|  |   [self assertInitFailsWithImage:mppImage | ||||||
|  |                            error:error | ||||||
|  |                    expectedError:[NSError errorWithDomain:kExpectedErrorDomain | ||||||
|  |                                                      code:MPPTasksErrorCodeInvalidArgumentError | ||||||
|  |                                                  userInfo:@{ | ||||||
|  |                                                    NSLocalizedDescriptionKey : | ||||||
|  |                                                        @"Pixel Buffer cannot be nil." | ||||||
|  |                                                  }]]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #pragma mark - Private | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Converts the input image in RGBA space into a `CMSampleBuffer`. | ||||||
|  |  * | ||||||
|  |  * @return `CMSampleBuffer` converted from the given `UIImage`. | ||||||
|  |  */ | ||||||
|  | - (CMSampleBufferRef)sampleBuffer { | ||||||
|  |   // Rotate the image and convert from RGBA to BGRA. | ||||||
|  |   CGImageRef CGImage = self.image.CGImage; | ||||||
|  |   size_t width = CGImageGetWidth(CGImage); | ||||||
|  |   size_t height = CGImageGetHeight(CGImage); | ||||||
|  |   size_t bpr = CGImageGetBytesPerRow(CGImage); | ||||||
|  | 
 | ||||||
|  |   CGDataProviderRef provider = CGImageGetDataProvider(CGImage); | ||||||
|  |   NSData *imageRGBAData = (id)CFBridgingRelease(CGDataProviderCopyData(provider)); | ||||||
|  |   const uint8_t order[4] = {2, 1, 0, 3}; | ||||||
|  | 
 | ||||||
|  |   NSData *imageBGRAData = nil; | ||||||
|  |   unsigned char *bgraPixel = (unsigned char *)malloc([imageRGBAData length]); | ||||||
|  |   if (bgraPixel) { | ||||||
|  |     vImage_Buffer src; | ||||||
|  |     src.height = height; | ||||||
|  |     src.width = width; | ||||||
|  |     src.rowBytes = bpr; | ||||||
|  |     src.data = (void *)[imageRGBAData bytes]; | ||||||
|  | 
 | ||||||
|  |     vImage_Buffer dest; | ||||||
|  |     dest.height = height; | ||||||
|  |     dest.width = width; | ||||||
|  |     dest.rowBytes = bpr; | ||||||
|  |     dest.data = bgraPixel; | ||||||
|  | 
 | ||||||
|  |     // Specify ordering changes in map. | ||||||
|  |     vImage_Error error = vImagePermuteChannels_ARGB8888(&src, &dest, order, kvImageNoFlags); | ||||||
|  | 
 | ||||||
|  |     // Package the result. | ||||||
|  |     if (error == kvImageNoError) { | ||||||
|  |       imageBGRAData = [NSData dataWithBytes:bgraPixel length:[imageRGBAData length]]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Memory cleanup. | ||||||
|  |     free(bgraPixel); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (imageBGRAData == nil) { | ||||||
|  |     XCTFail(@"Failed to convert input image."); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Write data to `CMSampleBuffer`. | ||||||
|  |   NSDictionary *options = @{ | ||||||
|  |     (__bridge NSString *)kCVPixelBufferCGImageCompatibilityKey : @(YES), | ||||||
|  |     (__bridge NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey : @(YES) | ||||||
|  |   }; | ||||||
|  |   CVPixelBufferRef pixelBuffer; | ||||||
|  |   CVReturn status = CVPixelBufferCreateWithBytes( | ||||||
|  |       kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, (void *)[imageBGRAData bytes], | ||||||
|  |       bpr, NULL, nil, (__bridge CFDictionaryRef)options, &pixelBuffer); | ||||||
|  | 
 | ||||||
|  |   if (status != kCVReturnSuccess) { | ||||||
|  |     XCTFail(@"Failed to create pixel buffer."); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   CVPixelBufferLockBaseAddress(pixelBuffer, 0); | ||||||
|  |   CMVideoFormatDescriptionRef videoInfo = NULL; | ||||||
|  |   CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, &videoInfo); | ||||||
|  | 
 | ||||||
|  |   CMSampleBufferRef buffer; | ||||||
|  |   CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, true, NULL, NULL, videoInfo, | ||||||
|  |                                      &kCMTimingInfoInvalid, &buffer); | ||||||
|  | 
 | ||||||
|  |   CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); | ||||||
|  | 
 | ||||||
|  |   return buffer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | NS_ASSUME_NONNULL_END | ||||||
|  | @ -4,8 +4,12 @@ licenses(["notice"]) | ||||||
| 
 | 
 | ||||||
| objc_library( | objc_library( | ||||||
|     name = "MPPImage", |     name = "MPPImage", | ||||||
|     srcs = ["sources/MPPImage.h"], |     srcs = ["sources/MPPImage.m"], | ||||||
|     hdrs = ["sources/MPPImage.m"], |     hdrs = ["sources/MPPImage.h"], | ||||||
|  |     copts = [ | ||||||
|  |         "-ObjC++", | ||||||
|  |         "-std=c++17", | ||||||
|  |     ], | ||||||
|     module_name = "MPPImage", |     module_name = "MPPImage", | ||||||
|     sdk_frameworks = [ |     sdk_frameworks = [ | ||||||
|         "CoreMedia", |         "CoreMedia", | ||||||
|  | @ -13,6 +17,7 @@ objc_library( | ||||||
|         "UIKit", |         "UIKit", | ||||||
|     ], |     ], | ||||||
|     deps = [ |     deps = [ | ||||||
|  |         "//mediapipe/tasks/ios/common:MPPCommon", | ||||||
|         "//mediapipe/tasks/ios/common/utils:MPPCommonUtils", |         "//mediapipe/tasks/ios/common/utils:MPPCommonUtils", | ||||||
|     ], |     ], | ||||||
|     copts = [ |     copts = [ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user