Allow piping cropping info through DesktopFrameIOSurface

Bug: webrtc:367915807
Change-Id: I13bc7c9b1ca5a8ef8147b7b754ef273061d3988f
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/365090
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Reviewed-by: Johannes Kron <kron@webrtc.org>
Commit-Queue: Andreas Pehrson <apehrson@mozilla.com>
Cr-Commit-Position: refs/heads/main@{#44352}
diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn
index 634ef53..ba14240 100644
--- a/modules/desktop_capture/BUILD.gn
+++ b/modules/desktop_capture/BUILD.gn
@@ -653,6 +653,7 @@
       "../../rtc_base:event_tracer",
       "../../rtc_base:logging",
       "../../rtc_base:macromagic",
+      "../../rtc_base:safe_conversions",
       "../../rtc_base:timeutils",
       "../../rtc_base/synchronization:mutex",
       "../../rtc_base/system:rtc_export",
diff --git a/modules/desktop_capture/mac/desktop_frame_iosurface.h b/modules/desktop_capture/mac/desktop_frame_iosurface.h
index 73da0f6..a696b35 100644
--- a/modules/desktop_capture/mac/desktop_frame_iosurface.h
+++ b/modules/desktop_capture/mac/desktop_frame_iosurface.h
@@ -24,9 +24,10 @@
 class DesktopFrameIOSurface final : public DesktopFrame {
  public:
   // Lock an IOSurfaceRef containing a snapshot of a display. Return NULL if
-  // failed to lock.
+  // failed to lock. `rect` specifies the portion of the surface that the
+  // DesktopFrame should be cropped to.
   static std::unique_ptr<DesktopFrameIOSurface> Wrap(
-      rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface);
+      rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface, CGRect rect = {});
 
   ~DesktopFrameIOSurface() override;
 
@@ -34,8 +35,16 @@
   DesktopFrameIOSurface& operator=(const DesktopFrameIOSurface&) = delete;
 
  private:
-  // This constructor expects `io_surface` to hold a non-null IOSurfaceRef.
-  explicit DesktopFrameIOSurface(rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface);
+  // `io_surface` must hold a non-null IOSurfaceRef that is already locked.
+  // `data` is the address of the first byte of data in `io_surface`'s locked
+  // buffer.
+  // `width` and `height` make up the dimensions of `io_surface` in pixels.
+  // `stride` is the number of bytes of a single row of pixels in `data`.
+  DesktopFrameIOSurface(rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface,
+                        uint8_t* data,
+                        int32_t width,
+                        int32_t height,
+                        int32_t stride);
 
   const rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface_;
 };
diff --git a/modules/desktop_capture/mac/desktop_frame_iosurface.mm b/modules/desktop_capture/mac/desktop_frame_iosurface.mm
index 11f2e9e..0fa8e30 100644
--- a/modules/desktop_capture/mac/desktop_frame_iosurface.mm
+++ b/modules/desktop_capture/mac/desktop_frame_iosurface.mm
@@ -12,12 +12,13 @@
 
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/numerics/safe_conversions.h"
 
 namespace webrtc {
 
 // static
 std::unique_ptr<DesktopFrameIOSurface> DesktopFrameIOSurface::Wrap(
-    rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface) {
+    rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface, CGRect rect) {
   if (!io_surface) {
     return nullptr;
   }
@@ -42,18 +43,50 @@
     return nullptr;
   }
 
-  return std::unique_ptr<DesktopFrameIOSurface>(
-      new DesktopFrameIOSurface(io_surface));
+  const size_t surface_width = IOSurfaceGetWidth(io_surface.get());
+  const size_t surface_height = IOSurfaceGetHeight(io_surface.get());
+  const int32_t stride =
+      checked_cast<int32_t>(IOSurfaceGetBytesPerRow(io_surface.get()));
+  uint8_t* const data =
+      static_cast<uint8_t*>(IOSurfaceGetBaseAddress(io_surface.get()));
+  int32_t width = checked_cast<int32_t>(surface_width);
+  int32_t height = checked_cast<int32_t>(surface_height);
+  ptrdiff_t offset = 0;
+  ptrdiff_t offset_columns = 0;
+  ptrdiff_t offset_rows = 0;
+  if (rect.size.width > 0 && rect.size.height > 0) {
+    width = checked_cast<int32_t>(std::floor(rect.size.width));
+    height = checked_cast<int32_t>(std::floor(rect.size.height));
+    offset_columns = checked_cast<ptrdiff_t>(std::ceil(rect.origin.x));
+    offset_rows = checked_cast<ptrdiff_t>(std::ceil(rect.origin.y));
+    offset = stride * offset_rows + bytes_per_pixel * offset_columns;
+  }
+
+  RTC_LOG(LS_VERBOSE) << "DesktopFrameIOSurface wrapping IOSurface with size "
+                      << surface_width << "x" << surface_height
+                      << ". Cropping to (" << offset_columns << ","
+                      << offset_rows << "; " << width << "x" << height
+                      << "). Stride=" << stride / bytes_per_pixel
+                      << ", buffer-offset-px=" << offset / bytes_per_pixel
+                      << ", buffer-offset-bytes=" << offset;
+
+  RTC_CHECK_GE(surface_width, offset_columns + width);
+  RTC_CHECK_GE(surface_height, offset_rows + height);
+  RTC_CHECK_GE(offset, 0);
+  RTC_CHECK_LE(offset + ((height - 1) * stride) + (width * bytes_per_pixel) - 1,
+               IOSurfaceGetAllocSize(io_surface.get()));
+
+  return std::unique_ptr<DesktopFrameIOSurface>(new DesktopFrameIOSurface(
+      io_surface, data + offset, width, height, stride));
 }
 
 DesktopFrameIOSurface::DesktopFrameIOSurface(
-    rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface)
-    : DesktopFrame(
-          DesktopSize(IOSurfaceGetWidth(io_surface.get()),
-                      IOSurfaceGetHeight(io_surface.get())),
-          IOSurfaceGetBytesPerRow(io_surface.get()),
-          static_cast<uint8_t*>(IOSurfaceGetBaseAddress(io_surface.get())),
-          nullptr),
+    rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface,
+    uint8_t* data,
+    int32_t width,
+    int32_t height,
+    int32_t stride)
+    : DesktopFrame(DesktopSize(width, height), stride, data, nullptr),
       io_surface_(io_surface) {
   RTC_DCHECK(io_surface_);
 }