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',