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:
parent
2767b53f66
commit
e6e749da38
116
webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc
Normal file
116
webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc
Normal 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
|
62
webrtc/modules/desktop_capture/desktop_and_cursor_composer.h
Normal file
62
webrtc/modules/desktop_capture/desktop_and_cursor_composer.h
Normal 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_
|
@ -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
|
@ -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",
|
||||
|
@ -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());
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user