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@4968 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
sergeyu@chromium.org 2013-10-16 02:48:41 +00:00
parent 2767b53f66
commit e6e749da38
7 changed files with 423 additions and 1 deletions

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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",

View File

@ -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());

View File

@ -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)

View File

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