diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc new file mode 100644 index 0000000000..3141fee8da --- /dev/null +++ b/webrtc/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 + +#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(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/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h new file mode 100644 index 0000000000..4f7c85bde3 --- /dev/null +++ b/webrtc/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 desktop_capturer_; + scoped_ptr mouse_monitor_; + + DesktopCapturer::Callback* callback_; + + scoped_ptr 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/webrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc b/webrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc new file mode 100644 index 0000000000..0bb723750a --- /dev/null +++ b/webrtc/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(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(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 image( + new BasicDesktopFrame(DesktopSize(kCursorWidth, kCursorHeight))); + uint32_t* data = reinterpret_cast(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 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/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi index 0ffef7f772..eb3bc9a29d 100644 --- a/webrtc/modules/desktop_capture/desktop_capture.gypi +++ b/webrtc/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/webrtc/modules/desktop_capture/desktop_geometry.cc b/webrtc/modules/desktop_capture/desktop_geometry.cc index 5811a8dac9..cffaefe1f1 100644 --- a/webrtc/modules/desktop_capture/desktop_geometry.cc +++ b/webrtc/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/webrtc/modules/desktop_capture/desktop_geometry.h b/webrtc/modules/desktop_capture/desktop_geometry.h index 2f87cfa065..580e0bfa71 100644 --- a/webrtc/modules/desktop_capture/desktop_geometry.h +++ b/webrtc/modules/desktop_capture/desktop_geometry.h @@ -35,6 +35,13 @@ class DesktopVector { 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 @@ class DesktopRect { 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 @@ class DesktopRect { 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/webrtc/modules/modules.gyp b/webrtc/modules/modules.gyp index e4bd95d2fa..5d80d31f0f 100644 --- a/webrtc/modules/modules.gyp +++ b/webrtc/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',