summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc')
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc479
1 files changed, 479 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc b/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc
new file mode 100644
index 0000000000..179e002bc5
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/desktop_and_cursor_composer_unittest.cc
@@ -0,0 +1,479 @@
+/*
+ * 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 "modules/desktop_capture/desktop_and_cursor_composer.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "modules/desktop_capture/desktop_capturer.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/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+namespace {
+
+using testing::ElementsAre;
+
+const int kFrameXCoord = 100;
+const int kFrameYCoord = 200;
+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(int width = kScreenWidth,
+ int height = kScreenHeight) {
+ DesktopFrame* frame = new BasicDesktopFrame(DesktopSize(width, height));
+ uint32_t* data = reinterpret_cast<uint32_t*>(frame->data());
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ *(data++) = GetFakeFramePixelValue(DesktopVector(x, y));
+ }
+ }
+ return frame;
+}
+
+MouseCursor* CreateTestCursor(DesktopVector hotspot) {
+ std::unique_ptr<DesktopFrame> image(
+ new BasicDesktopFrame(DesktopSize(kCursorWidth, kCursorHeight)));
+ uint32_t* data = reinterpret_cast<uint32_t*>(image->data());
+ // 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];
+ }
+ }
+ return new MouseCursor(image.release(), hotspot);
+}
+
+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_) {
+ callback_->OnMouseCursor(CreateTestCursor(hotspot_));
+ }
+ 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
+
+bool operator==(const DesktopRect& left, const DesktopRect& right) {
+ return left.equals(right);
+}
+
+std::ostream& operator<<(std::ostream& out, const DesktopRect& rect) {
+ out << "{" << rect.left() << "+" << rect.top() << "-" << rect.width() << "x"
+ << rect.height() << "}";
+ return out;
+}
+
+class DesktopAndCursorComposerTest : public ::testing::Test,
+ public DesktopCapturer::Callback {
+ public:
+ explicit DesktopAndCursorComposerTest(bool include_cursor = true)
+ : fake_screen_(new FakeScreenCapturer()),
+ fake_cursor_(include_cursor ? new FakeMouseMonitor() : nullptr),
+ blender_(fake_screen_, fake_cursor_) {
+ 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_;
+};
+
+class DesktopAndCursorComposerNoCursorMonitorTest
+ : public DesktopAndCursorComposerTest {
+ public:
+ DesktopAndCursorComposerNoCursorMonitorTest()
+ : DesktopAndCursorComposerTest(false) {}
+};
+
+TEST_F(DesktopAndCursorComposerTest, CursorShouldBeIgnoredIfNoFrameCaptured) {
+ 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()));
+
+ blender_.CaptureFrame();
+ // If capturer captured nothing, then cursor should be ignored, not matter
+ // its state or position.
+ EXPECT_EQ(frame_, nullptr);
+ }
+}
+
+TEST_F(DesktopAndCursorComposerTest, CursorShouldBeIgnoredIfFrameMayContainIt) {
+ // We can't use a shared frame because we need to detect modifications
+ // compared to a control.
+ std::unique_ptr<DesktopFrame> control_frame(CreateTestFrame());
+ control_frame->set_top_left(DesktopVector(kFrameXCoord, kFrameYCoord));
+
+ struct {
+ int x;
+ int y;
+ bool may_contain_cursor;
+ } tests[] = {
+ {100, 200, true},
+ {100, 200, false},
+ {150, 250, true},
+ {150, 250, false},
+ };
+
+ for (size_t i = 0; i < arraysize(tests); i++) {
+ SCOPED_TRACE(i);
+
+ std::unique_ptr<DesktopFrame> frame(CreateTestFrame());
+ frame->set_top_left(DesktopVector(kFrameXCoord, kFrameYCoord));
+ frame->set_may_contain_cursor(tests[i].may_contain_cursor);
+ fake_screen_->SetNextFrame(std::move(frame));
+
+ const DesktopVector abs_pos(tests[i].x, tests[i].y);
+ fake_cursor_->SetState(MouseCursorMonitor::INSIDE, abs_pos);
+ blender_.CaptureFrame();
+
+ // If the frame may already have contained the cursor, then `CaptureFrame()`
+ // should not have modified it, so it should be the same as the control.
+ EXPECT_TRUE(frame_);
+ const DesktopVector rel_pos(abs_pos.subtract(control_frame->top_left()));
+ if (tests[i].may_contain_cursor) {
+ EXPECT_EQ(
+ *reinterpret_cast<uint32_t*>(frame_->GetFrameDataAtPos(rel_pos)),
+ *reinterpret_cast<uint32_t*>(
+ control_frame->GetFrameDataAtPos(rel_pos)));
+
+ } else {
+ // `CaptureFrame()` should have modified the frame to have the cursor.
+ EXPECT_NE(
+ *reinterpret_cast<uint32_t*>(frame_->GetFrameDataAtPos(rel_pos)),
+ *reinterpret_cast<uint32_t*>(
+ control_frame->GetFrameDataAtPos(rel_pos)));
+ EXPECT_TRUE(frame_->may_contain_cursor());
+ }
+ }
+}
+
+TEST_F(DesktopAndCursorComposerTest,
+ CursorShouldBeIgnoredIfItIsOutOfDesktopFrame) {
+ std::unique_ptr<SharedDesktopFrame> frame(
+ SharedDesktopFrame::Wrap(CreateTestFrame()));
+ frame->set_top_left(DesktopVector(kFrameXCoord, kFrameYCoord));
+ // 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(DesktopAndCursorComposerTest, IsOccludedShouldBeConsidered) {
+ std::unique_ptr<SharedDesktopFrame> frame(
+ SharedDesktopFrame::Wrap(CreateTestFrame()));
+ frame->set_top_left(DesktopVector(kFrameXCoord, kFrameYCoord));
+ // 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(DesktopAndCursorComposerTest, CursorIncluded) {
+ std::unique_ptr<SharedDesktopFrame> frame(
+ SharedDesktopFrame::Wrap(CreateTestFrame()));
+ frame->set_top_left(DesktopVector(kFrameXCoord, kFrameYCoord));
+ // 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());
+ }
+}
+
+TEST_F(DesktopAndCursorComposerNoCursorMonitorTest,
+ UpdatedRegionIncludesOldAndNewCursorRectsIfMoved) {
+ std::unique_ptr<SharedDesktopFrame> frame(
+ SharedDesktopFrame::Wrap(CreateTestFrame()));
+ DesktopRect first_cursor_rect;
+ {
+ // Block to scope test_cursor, which is invalidated by OnMouseCursor.
+ MouseCursor* test_cursor = CreateTestCursor(DesktopVector(0, 0));
+ first_cursor_rect = DesktopRect::MakeSize(test_cursor->image()->size());
+ blender_.OnMouseCursor(test_cursor);
+ }
+ blender_.OnMouseCursorPosition(DesktopVector(0, 0));
+ fake_screen_->SetNextFrame(frame->Share());
+ blender_.CaptureFrame();
+
+ DesktopVector cursor_move_offset(1, 1);
+ DesktopRect second_cursor_rect = first_cursor_rect;
+ second_cursor_rect.Translate(cursor_move_offset);
+ blender_.OnMouseCursorPosition(cursor_move_offset);
+ fake_screen_->SetNextFrame(frame->Share());
+ blender_.CaptureFrame();
+
+ EXPECT_TRUE(frame->updated_region().is_empty());
+ DesktopRegion expected_region;
+ expected_region.AddRect(first_cursor_rect);
+ expected_region.AddRect(second_cursor_rect);
+ EXPECT_TRUE(frame_->updated_region().Equals(expected_region));
+}
+
+TEST_F(DesktopAndCursorComposerNoCursorMonitorTest,
+ UpdatedRegionIncludesOldAndNewCursorRectsIfShapeChanged) {
+ std::unique_ptr<SharedDesktopFrame> frame(
+ SharedDesktopFrame::Wrap(CreateTestFrame()));
+ DesktopRect first_cursor_rect;
+ {
+ // Block to scope test_cursor, which is invalidated by OnMouseCursor.
+ MouseCursor* test_cursor = CreateTestCursor(DesktopVector(0, 0));
+ first_cursor_rect = DesktopRect::MakeSize(test_cursor->image()->size());
+ blender_.OnMouseCursor(test_cursor);
+ }
+ blender_.OnMouseCursorPosition(DesktopVector(0, 0));
+ fake_screen_->SetNextFrame(frame->Share());
+ blender_.CaptureFrame();
+
+ // Create a second cursor, the same shape as the first. Since the code doesn't
+ // compare the cursor pixels, this is sufficient, and avoids needing two test
+ // cursor bitmaps.
+ DesktopRect second_cursor_rect;
+ {
+ MouseCursor* test_cursor = CreateTestCursor(DesktopVector(0, 0));
+ second_cursor_rect = DesktopRect::MakeSize(test_cursor->image()->size());
+ blender_.OnMouseCursor(test_cursor);
+ }
+ fake_screen_->SetNextFrame(frame->Share());
+ blender_.CaptureFrame();
+
+ EXPECT_TRUE(frame->updated_region().is_empty());
+ DesktopRegion expected_region;
+ expected_region.AddRect(first_cursor_rect);
+ expected_region.AddRect(second_cursor_rect);
+ EXPECT_TRUE(frame_->updated_region().Equals(expected_region));
+}
+
+TEST_F(DesktopAndCursorComposerNoCursorMonitorTest,
+ UpdatedRegionUnchangedIfCursorUnchanged) {
+ std::unique_ptr<SharedDesktopFrame> frame(
+ SharedDesktopFrame::Wrap(CreateTestFrame()));
+ blender_.OnMouseCursor(CreateTestCursor(DesktopVector(0, 0)));
+ blender_.OnMouseCursorPosition(DesktopVector(0, 0));
+ fake_screen_->SetNextFrame(frame->Share());
+ blender_.CaptureFrame();
+ fake_screen_->SetNextFrame(frame->Share());
+ blender_.CaptureFrame();
+
+ EXPECT_TRUE(frame->updated_region().is_empty());
+ EXPECT_TRUE(frame_->updated_region().is_empty());
+}
+
+} // namespace webrtc