blob: 0725facfcb10225307f3e38253d77348c7425ee8 [file] [log] [blame]
/*
* 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 <memory>
#include "modules/desktop_capture/desktop_and_cursor_composer.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/mouse_cursor.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "rtc_base/arraysize.h"
#include "test/gtest.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.GetFrameDataAtPos(pos));
}
// 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;
}
DesktopFrame* CreateTestFrame() {
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));
}
}
return frame;
}
class FakeScreenCapturer : public DesktopCapturer {
public:
FakeScreenCapturer() {}
void Start(Callback* callback) override { callback_ = callback; }
void CaptureFrame() override {
callback_->OnCaptureResult(
next_frame_ ? Result::SUCCESS : Result::ERROR_TEMPORARY,
std::move(next_frame_));
}
void SetNextFrame(std::unique_ptr<DesktopFrame> next_frame) {
next_frame_ = std::move(next_frame);
}
bool IsOccluded(const DesktopVector& pos) override { return is_occluded_; }
void set_is_occluded(bool value) { is_occluded_ = value; }
private:
Callback* callback_ = nullptr;
std::unique_ptr<DesktopFrame> next_frame_;
bool is_occluded_ = false;
};
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;
}
void Init(Callback* callback, Mode mode) override { callback_ = callback; }
void Capture() override {
if (changed_) {
std::unique_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_);
callback_->OnMouseCursorPosition(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));
}
}
}
}
} // namespace
template <bool use_desktop_relative_cursor_position>
class DesktopAndCursorComposerTest : public testing::Test,
public DesktopCapturer::Callback {
public:
DesktopAndCursorComposerTest()
: fake_screen_(new FakeScreenCapturer()),
fake_cursor_(new FakeMouseMonitor()),
blender_(fake_screen_,
fake_cursor_,
use_desktop_relative_cursor_position) {
blender_.Start(this);
}
// DesktopCapturer::Callback interface
void OnCaptureResult(DesktopCapturer::Result result,
std::unique_ptr<DesktopFrame> frame) override {
frame_ = std::move(frame);
}
protected:
// Owned by |blender_|.
FakeScreenCapturer* fake_screen_;
FakeMouseMonitor* fake_cursor_;
DesktopAndCursorComposer blender_;
std::unique_ptr<DesktopFrame> frame_;
};
using DesktopAndCursorComposerWithRelativePositionTest =
DesktopAndCursorComposerTest<false>;
// Verify DesktopAndCursorComposer can handle the case when the screen capturer
// fails.
TEST_F(DesktopAndCursorComposerWithRelativePositionTest, Error) {
fake_cursor_->SetHotspot(DesktopVector());
fake_cursor_->SetState(MouseCursorMonitor::INSIDE, DesktopVector());
fake_screen_->SetNextFrame(nullptr);
blender_.CaptureFrame();
EXPECT_FALSE(frame_);
}
TEST_F(DesktopAndCursorComposerWithRelativePositionTest, 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},
};
for (size_t i = 0; i < arraysize(tests); 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);
std::unique_ptr<SharedDesktopFrame> frame(
SharedDesktopFrame::Wrap(CreateTestFrame()));
fake_screen_->SetNextFrame(frame->Share());
blender_.CaptureFrame();
VerifyFrame(*frame_, state, pos);
// Verify that the cursor is erased before the frame buffer is returned to
// the screen capturer.
frame_.reset();
VerifyFrame(*frame, MouseCursorMonitor::OUTSIDE, DesktopVector());
}
}
using DesktopAndCursorComposerWithAbsolutePositionTest =
DesktopAndCursorComposerTest<true>;
TEST_F(DesktopAndCursorComposerWithAbsolutePositionTest,
CursorShouldBeIgnoredIfItIsOutOfDesktopFrame) {
std::unique_ptr<SharedDesktopFrame> frame(
SharedDesktopFrame::Wrap(CreateTestFrame()));
frame->set_top_left(DesktopVector(100, 200));
// The frame covers (100, 200) - (200, 300).
struct {
int x;
int y;
} tests[] = {
{ 0, 0 },
{ 50, 50 },
{ 50, 150 },
{ 100, 150 },
{ 50, 200 },
{ 99, 200 },
{ 100, 199 },
{ 200, 300 },
{ 200, 299 },
{ 199, 300 },
{ -1, -1 },
{ -10000, -10000 },
{ 10000, 10000 },
};
for (size_t i = 0; i < arraysize(tests); i++) {
SCOPED_TRACE(i);
fake_screen_->SetNextFrame(frame->Share());
// The CursorState is ignored when using absolute cursor position.
fake_cursor_->SetState(MouseCursorMonitor::OUTSIDE,
DesktopVector(tests[i].x, tests[i].y));
blender_.CaptureFrame();
VerifyFrame(*frame_, MouseCursorMonitor::OUTSIDE, DesktopVector(0, 0));
}
}
TEST_F(DesktopAndCursorComposerWithAbsolutePositionTest,
IsOccludedShouldBeConsidered) {
std::unique_ptr<SharedDesktopFrame> frame(
SharedDesktopFrame::Wrap(CreateTestFrame()));
frame->set_top_left(DesktopVector(100, 200));
// The frame covers (100, 200) - (200, 300).
struct {
int x;
int y;
} tests[] = {
{ 100, 200 },
{ 101, 200 },
{ 100, 201 },
{ 101, 201 },
{ 150, 250 },
{ 199, 299 },
};
fake_screen_->set_is_occluded(true);
for (size_t i = 0; i < arraysize(tests); i++) {
SCOPED_TRACE(i);
fake_screen_->SetNextFrame(frame->Share());
// The CursorState is ignored when using absolute cursor position.
fake_cursor_->SetState(MouseCursorMonitor::OUTSIDE,
DesktopVector(tests[i].x, tests[i].y));
blender_.CaptureFrame();
VerifyFrame(*frame_, MouseCursorMonitor::OUTSIDE, DesktopVector());
}
}
TEST_F(DesktopAndCursorComposerWithAbsolutePositionTest, CursorIncluded) {
std::unique_ptr<SharedDesktopFrame> frame(
SharedDesktopFrame::Wrap(CreateTestFrame()));
frame->set_top_left(DesktopVector(100, 200));
// The frame covers (100, 200) - (200, 300).
struct {
int x;
int y;
} tests[] = {
{ 100, 200 },
{ 101, 200 },
{ 100, 201 },
{ 101, 201 },
{ 150, 250 },
{ 199, 299 },
};
for (size_t i = 0; i < arraysize(tests); i++) {
SCOPED_TRACE(i);
const DesktopVector abs_pos(tests[i].x, tests[i].y);
const DesktopVector rel_pos(abs_pos.subtract(frame->top_left()));
fake_screen_->SetNextFrame(frame->Share());
// The CursorState is ignored when using absolute cursor position.
fake_cursor_->SetState(MouseCursorMonitor::OUTSIDE, abs_pos);
blender_.CaptureFrame();
VerifyFrame(*frame_, MouseCursorMonitor::INSIDE, rel_pos);
// Verify that the cursor is erased before the frame buffer is returned to
// the screen capturer.
frame_.reset();
VerifyFrame(*frame, MouseCursorMonitor::OUTSIDE, DesktopVector());
}
}
} // namespace webrtc