Add MouseCursorRenderer.

The new class acts as a wrapper for DesktopCapturer interface. It takes
mouse shape and position from MouseCursorCapturer and renders it on the
frames produced by underlying DesktopCapturer.

BUG=crbug.com/173265
R=wez@chromium.org
TBR=andrew@webrtc.org (modules.gyp)

Review URL: https://webrtc-codereview.appspot.com/2387004

git-svn-id: http://webrtc.googlecode.com/svn/trunk/webrtc@4968 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/modules/desktop_capture/desktop_and_cursor_composer.cc b/modules/desktop_capture/desktop_and_cursor_composer.cc
new file mode 100644
index 0000000..3141fee
--- /dev/null
+++ b/modules/desktop_capture/desktop_and_cursor_composer.cc
@@ -0,0 +1,116 @@
+/*
+ *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/desktop_capture/desktop_and_cursor_composer.h"
+
+#include <string.h>
+
+#include "webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+
+namespace webrtc {
+
+namespace {
+
+// Helper function that blends one image into another. Source image must be
+// pre-multiplied with the alpha channel. Destination is assumed to be opaque.
+void AlphaBlend(uint8_t* dest, int dest_stride,
+                const uint8_t* src, int src_stride,
+                const DesktopSize& size) {
+  for (int y = 0; y < size.height(); ++y) {
+    for (int x = 0; x < size.width(); ++x) {
+      uint32_t base_alpha = 255 - src[x * DesktopFrame::kBytesPerPixel + 3];
+      if (base_alpha == 255) {
+        continue;
+      } else if (base_alpha == 0) {
+        memcpy(dest + x * DesktopFrame::kBytesPerPixel,
+               src + x * DesktopFrame::kBytesPerPixel,
+               DesktopFrame::kBytesPerPixel);
+      } else {
+        dest[x * DesktopFrame::kBytesPerPixel] =
+            dest[x * DesktopFrame::kBytesPerPixel] * base_alpha / 255 +
+            src[x * DesktopFrame::kBytesPerPixel];
+        dest[x * DesktopFrame::kBytesPerPixel + 1] =
+            dest[x * DesktopFrame::kBytesPerPixel + 1] * base_alpha / 255 +
+            src[x * DesktopFrame::kBytesPerPixel + 1];
+        dest[x * DesktopFrame::kBytesPerPixel + 2] =
+            dest[x * DesktopFrame::kBytesPerPixel + 2] * base_alpha / 255 +
+            src[x * DesktopFrame::kBytesPerPixel + 2];
+      }
+    }
+    src += src_stride;
+    dest += dest_stride;
+  }
+}
+
+}  // namespace
+
+DesktopAndCursorComposer::DesktopAndCursorComposer(
+    DesktopCapturer* desktop_capturer,
+    MouseCursorMonitor* mouse_monitor)
+    : desktop_capturer_(desktop_capturer),
+      mouse_monitor_(mouse_monitor) {
+}
+
+DesktopAndCursorComposer::~DesktopAndCursorComposer() {}
+
+void DesktopAndCursorComposer::Start(DesktopCapturer::Callback* callback) {
+  callback_ = callback;
+  if (mouse_monitor_.get())
+    mouse_monitor_->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION);
+  desktop_capturer_->Start(this);
+}
+
+void DesktopAndCursorComposer::Capture(const DesktopRegion& region) {
+  if (mouse_monitor_.get())
+    mouse_monitor_->Capture();
+  desktop_capturer_->Capture(region);
+}
+
+SharedMemory* DesktopAndCursorComposer::CreateSharedMemory(size_t size) {
+  return callback_->CreateSharedMemory(size);
+}
+
+void DesktopAndCursorComposer::OnCaptureCompleted(DesktopFrame* frame) {
+  if (cursor_.get() && cursor_state_ == MouseCursorMonitor::INSIDE) {
+    DesktopVector image_pos = cursor_position_.subtract(cursor_->hotspot());
+    DesktopRect target_rect = DesktopRect::MakeSize(cursor_->image().size());
+    target_rect.Translate(image_pos);
+    DesktopVector target_origin = target_rect.top_left();
+    target_rect.IntersectWith(DesktopRect::MakeSize(frame->size()));
+    DesktopVector origin_shift = target_rect.top_left().subtract(target_origin);
+    int cursor_width = cursor_->image().size().width();
+    AlphaBlend(reinterpret_cast<uint8_t*>(frame->data()) +
+                   target_rect.top() * frame->stride() +
+                   target_rect.left() * DesktopFrame::kBytesPerPixel,
+               frame->stride(),
+               cursor_->image().data() +
+                   (origin_shift.y() * cursor_width + origin_shift.x()) *
+                       DesktopFrame::kBytesPerPixel,
+               cursor_width * DesktopFrame::kBytesPerPixel,
+               target_rect.size());
+  }
+
+  callback_->OnCaptureCompleted(frame);
+}
+
+void DesktopAndCursorComposer::OnMouseCursor(MouseCursor* cursor) {
+  cursor_.reset(cursor);
+}
+
+void DesktopAndCursorComposer::OnMouseCursorPosition(
+    MouseCursorMonitor::CursorState state,
+    const DesktopVector& position) {
+  cursor_state_ = state;
+  cursor_position_ = position;
+}
+
+}  // namespace webrtc
diff --git a/modules/desktop_capture/desktop_and_cursor_composer.h b/modules/desktop_capture/desktop_and_cursor_composer.h
new file mode 100644
index 0000000..4f7c85b
--- /dev/null
+++ b/modules/desktop_capture/desktop_and_cursor_composer.h
@@ -0,0 +1,62 @@
+/*
+ *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_
+#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_
+
+#include "webrtc/modules/desktop_capture/desktop_capturer.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
+#include "webrtc/system_wrappers/interface/scoped_ptr.h"
+
+namespace webrtc {
+
+// A wrapper for DesktopCapturer that also captures mouse using specified
+// MouseCursorMonitor and renders it on the generated streams.
+class DesktopAndCursorComposer : public DesktopCapturer,
+                            public DesktopCapturer::Callback,
+                            public MouseCursorMonitor::Callback {
+ public:
+  // Creates a new blender that captures mouse cursor using |mouse_monitor| and
+  // renders it into the frames generated by |desktop_capturer|. If
+  // |mouse_monitor| is NULL the frames are passed unmodified. Takes ownership
+  // of both arguments.
+  DesktopAndCursorComposer(DesktopCapturer* desktop_capturer,
+                      MouseCursorMonitor* mouse_monitor);
+  virtual ~DesktopAndCursorComposer();
+
+  // DesktopCapturer interface.
+  virtual void Start(DesktopCapturer::Callback* callback) OVERRIDE;
+  virtual void Capture(const DesktopRegion& region) OVERRIDE;
+
+ private:
+  // DesktopCapturer::Callback interface.
+  virtual SharedMemory* CreateSharedMemory(size_t size) OVERRIDE;
+  virtual void OnCaptureCompleted(DesktopFrame* frame) OVERRIDE;
+
+  // MouseCursorMonitor::Callback interface.
+  virtual void OnMouseCursor(MouseCursor* cursor) OVERRIDE;
+  virtual void OnMouseCursorPosition(MouseCursorMonitor::CursorState state,
+                                     const DesktopVector& position) OVERRIDE;
+
+  scoped_ptr<DesktopCapturer> desktop_capturer_;
+  scoped_ptr<MouseCursorMonitor> mouse_monitor_;
+
+  DesktopCapturer::Callback* callback_;
+
+  scoped_ptr<MouseCursor> cursor_;
+  MouseCursorMonitor::CursorState cursor_state_;
+  DesktopVector cursor_position_;
+
+  DISALLOW_COPY_AND_ASSIGN(DesktopAndCursorComposer);
+};
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_
diff --git a/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc b/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc
new file mode 100644
index 0000000..0bb7237
--- /dev/null
+++ b/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc
@@ -0,0 +1,221 @@
+/*
+ *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/desktop_capture/desktop_and_cursor_composer.h"
+
+#include "gtest/gtest.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
+#include "webrtc/modules/desktop_capture/desktop_frame.h"
+#include "webrtc/modules/desktop_capture/mouse_cursor.h"
+#include "webrtc/modules/desktop_capture/window_capturer.h"
+#include "webrtc/system_wrappers/interface/logging.h"
+#include "webrtc/system_wrappers/interface/scoped_ptr.h"
+
+namespace webrtc {
+
+namespace {
+
+const int kScreenWidth = 100;
+const int kScreenHeight = 100;
+const int kCursorWidth = 10;
+const int kCursorHeight = 10;
+
+const int kTestCursorSize = 3;
+const uint32_t kTestCursorData[kTestCursorSize][kTestCursorSize] = {
+  { 0xffffffff, 0x99990000, 0xaa222222, },
+  { 0x88008800, 0xaa0000aa, 0xaa333333, },
+  { 0x00000000, 0xaa0000aa, 0xaa333333, },
+};
+
+uint32_t GetFakeFramePixelValue(const DesktopVector& p) {
+  uint32_t r = 100 + p.x();
+  uint32_t g = 100 + p.y();
+  uint32_t b = 100 + p.x() + p.y();
+  return b + (g << 8) + (r << 16) + 0xff000000;
+}
+
+uint32_t GetFramePixel(const DesktopFrame& frame, const DesktopVector& pos) {
+  return *reinterpret_cast<uint32_t*>(frame.data() + pos.y() * frame.stride() +
+                                      pos.x() * DesktopFrame::kBytesPerPixel);
+}
+
+// Blends two pixel values taking into account alpha.
+uint32_t BlendPixels(uint32_t dest, uint32_t src) {
+  uint8_t alpha = 255 - ((src & 0xff000000) >> 24);
+  uint32_t r =
+      ((dest & 0x00ff0000) >> 16) * alpha / 255 + ((src & 0x00ff0000) >> 16);
+  uint32_t g =
+      ((dest & 0x0000ff00) >> 8) * alpha / 255 + ((src & 0x0000ff00) >> 8);
+  uint32_t b = (dest & 0x000000ff) * alpha / 255 + (src & 0x000000ff);
+  return b + (g << 8) + (r << 16) + 0xff000000;
+}
+
+class FakeScreenCapturer : public DesktopCapturer {
+ public:
+  FakeScreenCapturer() {}
+
+  virtual void Start(Callback* callback) OVERRIDE {
+    callback_ = callback;
+  }
+
+  virtual void Capture(const DesktopRegion& region) OVERRIDE {
+    DesktopFrame* frame =
+        new BasicDesktopFrame(DesktopSize(kScreenWidth, kScreenHeight));
+    uint32_t* data = reinterpret_cast<uint32_t*>(frame->data());
+    for (int y = 0; y < kScreenHeight; ++y) {
+      for (int x = 0; x < kScreenWidth; ++x) {
+        *(data++) = GetFakeFramePixelValue(DesktopVector(x, y));
+      }
+    }
+    callback_->OnCaptureCompleted(frame);
+  }
+
+ private:
+  Callback* callback_;
+};
+
+class FakeMouseMonitor : public MouseCursorMonitor {
+ public:
+  FakeMouseMonitor() : changed_(true) {}
+
+  void SetState(CursorState state, const DesktopVector& pos) {
+    state_ = state;
+    position_ = pos;
+  }
+
+  void SetHotspot(const DesktopVector& hotspot) {
+    if (!hotspot_.equals(hotspot))
+      changed_ = true;
+    hotspot_ = hotspot;
+  }
+
+  virtual void Init(Callback* callback, Mode mode) OVERRIDE {
+    callback_ = callback;
+  }
+
+  virtual void Capture() OVERRIDE {
+    if (changed_) {
+      scoped_ptr<DesktopFrame> image(
+          new BasicDesktopFrame(DesktopSize(kCursorWidth, kCursorHeight)));
+      uint32_t* data = reinterpret_cast<uint32_t*>(image->data());
+      memset(data, 0, image->stride() * kCursorHeight);
+
+      // Set four pixels near the hotspot and leave all other blank.
+      for (int y = 0; y < kTestCursorSize; ++y) {
+        for (int x = 0; x < kTestCursorSize; ++x) {
+          data[(hotspot_.y() + y) * kCursorWidth + (hotspot_.x() + x)] =
+              kTestCursorData[y][x];
+        }
+      }
+
+      callback_->OnMouseCursor(new MouseCursor(image.release(), hotspot_));
+    }
+
+    callback_->OnMouseCursorPosition(state_, position_);
+  }
+
+ private:
+  Callback* callback_;
+  CursorState state_;
+  DesktopVector position_;
+  DesktopVector hotspot_;
+  bool changed_;
+};
+
+void VerifyFrame(const DesktopFrame& frame,
+                 MouseCursorMonitor::CursorState state,
+                 const DesktopVector& pos) {
+  // Verify that all other pixels are set to their original values.
+  DesktopRect image_rect =
+      DesktopRect::MakeWH(kTestCursorSize, kTestCursorSize);
+  image_rect.Translate(pos);
+
+  for (int y = 0; y < kScreenHeight; ++y) {
+    for (int x = 0; x < kScreenWidth; ++x) {
+      DesktopVector p(x, y);
+      if (state == MouseCursorMonitor::INSIDE && image_rect.Contains(p)) {
+        EXPECT_EQ(BlendPixels(GetFakeFramePixelValue(p),
+                              kTestCursorData[y - pos.y()][x - pos.x()]),
+                  GetFramePixel(frame, p));
+      } else {
+        EXPECT_EQ(GetFakeFramePixelValue(p), GetFramePixel(frame, p));
+      }
+    }
+  }
+}
+
+class DesktopAndCursorComposerTest : public testing::Test,
+                                     public DesktopCapturer::Callback {
+ public:
+  DesktopAndCursorComposerTest()
+      : fake_cursor_(new FakeMouseMonitor()),
+        blender_(new FakeScreenCapturer(), fake_cursor_) {
+  }
+
+  // DesktopCapturer::Callback interface
+  virtual SharedMemory* CreateSharedMemory(size_t size) OVERRIDE {
+    return NULL;
+  }
+
+  virtual void OnCaptureCompleted(DesktopFrame* frame) OVERRIDE {
+    frame_.reset(frame);
+  }
+
+ protected:
+  // Owned by |blender_|.
+  FakeMouseMonitor* fake_cursor_;
+  DesktopAndCursorComposer blender_;
+  scoped_ptr<DesktopFrame> frame_;
+};
+
+TEST_F(DesktopAndCursorComposerTest, Blend) {
+  struct {
+    int x, y;
+    int hotspot_x, hotspot_y;
+    bool inside;
+  } tests[] = {
+    {0, 0, 0, 0, true},
+    {50, 50, 0, 0, true},
+    {100, 50, 0, 0, true},
+    {50, 100, 0, 0, true},
+    {100, 100, 0, 0, true},
+    {0, 0, 2, 5, true},
+    {1, 1, 2, 5, true},
+    {50, 50, 2, 5, true},
+    {100, 100, 2, 5, true},
+    {0, 0, 5, 2, true},
+    {50, 50, 5, 2, true},
+    {100, 100, 5, 2, true},
+    {0, 0, 0, 0, false},
+  };
+
+  blender_.Start(this);
+
+  for (size_t i = 0; i < (sizeof(tests) / sizeof(tests[0])); ++i) {
+    SCOPED_TRACE(i);
+
+    DesktopVector hotspot(tests[i].hotspot_x, tests[i].hotspot_y);
+    fake_cursor_->SetHotspot(hotspot);
+
+    MouseCursorMonitor::CursorState state = tests[i].inside
+                                                ? MouseCursorMonitor::INSIDE
+                                                : MouseCursorMonitor::OUTSIDE;
+    DesktopVector pos(tests[i].x, tests[i].y);
+    fake_cursor_->SetState(state, pos);
+
+    blender_.Capture(DesktopRegion());
+
+    VerifyFrame(*frame_, state, pos);
+  }
+}
+
+}  // namespace
+
+}  // namespace webrtc
diff --git a/modules/desktop_capture/desktop_capture.gypi b/modules/desktop_capture/desktop_capture.gypi
index 0ffef7f..eb3bc9a 100644
--- a/modules/desktop_capture/desktop_capture.gypi
+++ b/modules/desktop_capture/desktop_capture.gypi
@@ -15,6 +15,8 @@
         '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers',
       ],
       'sources': [
+        "desktop_and_cursor_composer.cc",
+        "desktop_and_cursor_composer.h",
         "desktop_capture_types.h",
         "desktop_capturer.h",
         "desktop_frame.cc",
diff --git a/modules/desktop_capture/desktop_geometry.cc b/modules/desktop_capture/desktop_geometry.cc
index 5811a8d..cffaefe 100644
--- a/modules/desktop_capture/desktop_geometry.cc
+++ b/modules/desktop_capture/desktop_geometry.cc
@@ -14,6 +14,11 @@
 
 namespace webrtc {
 
+bool DesktopRect::Contains(const DesktopVector& point) const {
+  return point.x() >= left() && point.x() < right() &&
+         point.y() >= top() && point.y() < bottom();
+}
+
 void DesktopRect::IntersectWith(const DesktopRect& rect) {
   left_ = std::max(left(), rect.left());
   top_ = std::max(top(), rect.top());
diff --git a/modules/desktop_capture/desktop_geometry.h b/modules/desktop_capture/desktop_geometry.h
index 2f87cfa..580e0bf 100644
--- a/modules/desktop_capture/desktop_geometry.h
+++ b/modules/desktop_capture/desktop_geometry.h
@@ -35,6 +35,13 @@
     y_ = y;
   }
 
+  DesktopVector add(const DesktopVector& other) const {
+    return DesktopVector(x() + other.x(), y() + other.y());
+  }
+  DesktopVector subtract(const DesktopVector& other) const {
+    return DesktopVector(x() - other.x(), y() - other.y());
+  }
+
  private:
   int32_t x_;
   int32_t y_;
@@ -94,6 +101,9 @@
   int32_t width() const { return right_ - left_; }
   int32_t height() const { return bottom_ - top_; }
 
+  DesktopVector top_left() const { return DesktopVector(left_, top_); }
+  DesktopSize size() const { return DesktopSize(width(), height()); }
+
   bool is_empty() const { return left_ >= right_ || top_ >= bottom_; }
 
   bool equals(const DesktopRect& other) const {
@@ -101,11 +111,15 @@
         right_ == other.right_ && bottom_ == other.bottom_;
   }
 
+  // Returns true if |point| lies within the rectangle boundaries.
+  bool Contains(const DesktopVector& point) const;
+
   // Finds intersection with |rect|.
   void IntersectWith(const DesktopRect& rect);
 
   // Adds (dx, dy) to the position of the rectangle.
   void Translate(int32_t dx, int32_t dy);
+  void Translate(DesktopVector d) { Translate(d.x(), d.y()); };
 
  private:
   DesktopRect(int32_t left, int32_t top, int32_t right, int32_t bottom)
diff --git a/modules/modules.gyp b/modules/modules.gyp
index e4bd95d..5d80d31 100644
--- a/modules/modules.gyp
+++ b/modules/modules.gyp
@@ -156,10 +156,11 @@
             'audio_processing/utility/delay_estimator_unittest.cc',
             'audio_processing/utility/ring_buffer_unittest.cc',
             'bitrate_controller/bitrate_controller_unittest.cc',
-            'desktop_capture/mouse_cursor_monitor_unittest.cc',
+            'desktop_capture/desktop_and_cursor_composer_unittest.cc',
             'desktop_capture/desktop_region_unittest.cc',
             'desktop_capture/differ_block_unittest.cc',
             'desktop_capture/differ_unittest.cc',
+            'desktop_capture/mouse_cursor_monitor_unittest.cc',
             'desktop_capture/screen_capturer_helper_unittest.cc',
             'desktop_capture/screen_capturer_mac_unittest.cc',
             'desktop_capture/screen_capturer_mock_objects.h',
@@ -238,6 +239,7 @@
             # supported.
             ['desktop_capture_supported==0', {
               'sources!': [
+                'desktop_capture/desktop_and_cursor_composer_unittest.cc',
                 'desktop_capture/mouse_cursor_monitor_unittest.cc',
                 'desktop_capture/screen_capturer_helper_unittest.cc',
                 'desktop_capture/screen_capturer_mac_unittest.cc',