#include "base/memory/scoped_ptr.h"
#include "remoting/host/differ.h"
#include "remoting/host/differ_block.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace remoting {
const int kScreenWidth= 96;
const int kScreenHeight = 96;
const int kPartialScreenWidth = 70;
const int kPartialScreenHeight = 70;
class DifferTest : public testing::Test {
public:
DifferTest() {
}
protected:
void InitDiffer(int width, int height) {
width_ = width;
height_ = height;
bytes_per_pixel_ = kBytesPerPixel;
stride_ = (kBytesPerPixel * width);
buffer_size_ = width_ * height_ * bytes_per_pixel_;
differ_.reset(new Differ(width_, height_, bytes_per_pixel_, stride_));
prev_.reset(new uint8[buffer_size_]);
memset(prev_.get(), 0, buffer_size_);
curr_.reset(new uint8[buffer_size_]);
memset(curr_.get(), 0, buffer_size_);
}
void ClearBuffer(uint8* buffer) {
memset(buffer, 0, buffer_size_);
}
void MarkDirtyBlocks(const void* prev_buffer, const void* curr_buffer) {
differ_->MarkDirtyBlocks(prev_buffer, curr_buffer);
}
void MergeBlocks(SkRegion* dirty) {
differ_->MergeBlocks(dirty);
}
int RegionRectCount(const SkRegion& region) {
int count = 0;
for(SkRegion::Iterator iter(region); !iter.done(); iter.next()) {
++count;
}
return count;
}
DiffInfo DiffBlock(int block_x, int block_y) {
int block_offset = ((block_y * stride_) + (block_x * bytes_per_pixel_))
* kBlockSize;
return BlockDifference(prev_.get() + block_offset,
curr_.get() + block_offset,
stride_);
}
void WriteBlockPixel(uint8* buffer, int block_x, int block_y,
int pixel_x, int pixel_y, uint32 value) {
WritePixel(buffer, (block_x * kBlockSize) + pixel_x,
(block_y * kBlockSize) + pixel_y, value);
}
void WritePixel(uint8* buffer, int x, int y, uint32 value) {
uint8* pixel = reinterpret_cast<uint8*>(&value);
buffer += (y * stride_) + (x * bytes_per_pixel_);
for (int b = bytes_per_pixel_ - 1; b >= 0; b--) {
*buffer++ = pixel[b];
}
}
void ClearDiffInfo() {
memset(differ_->diff_info_.get(), 0, differ_->diff_info_size_);
}
DiffInfo GetDiffInfo(int x, int y) {
DiffInfo* diff_info = differ_->diff_info_.get();
return diff_info[(y * GetDiffInfoWidth()) + x];
}
int GetDiffInfoWidth() {
return differ_->diff_info_width_;
}
int GetDiffInfoHeight() {
return differ_->diff_info_height_;
}
int GetDiffInfoSize() {
return differ_->diff_info_size_;
}
void SetDiffInfo(int x, int y, const DiffInfo& value) {
DiffInfo* diff_info = differ_->diff_info_.get();
diff_info[(y * GetDiffInfoWidth()) + x] = value;
}
void MarkBlocks(int x_origin, int y_origin, int width, int height) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
SetDiffInfo(x_origin + x, y_origin + y, 1);
}
}
}
bool CheckDirtyRegionContainsRect(const SkRegion& region, int x, int y,
int width, int height) {
SkIRect r = SkIRect::MakeXYWH(x * kBlockSize, y * kBlockSize,
width * kBlockSize, height * kBlockSize);
bool found = false;
for (SkRegion::Iterator i(region); !found && !i.done(); i.next()) {
found = (i.rect() == r);
}
return found;
}
bool MarkBlocksAndCheckMerge(int x_origin, int y_origin,
int width, int height) {
ClearDiffInfo();
MarkBlocks(x_origin, y_origin, width, height);
SkRegion dirty;
MergeBlocks(&dirty);
bool is_good = dirty.isRect();
if (is_good) {
is_good = CheckDirtyRegionContainsRect(dirty, x_origin, y_origin,
width, height);
}
return is_good;
}
scoped_ptr<Differ> differ_;
int width_;
int height_;
int bytes_per_pixel_;
int stride_;
int buffer_size_;
scoped_array<uint8> prev_;
scoped_array<uint8> curr_;
private:
DISALLOW_COPY_AND_ASSIGN(DifferTest);
};
TEST_F(DifferTest, Setup) {
InitDiffer(kScreenWidth, kScreenHeight);
EXPECT_EQ(4, GetDiffInfoWidth());
EXPECT_EQ(4, GetDiffInfoHeight());
EXPECT_EQ(16, GetDiffInfoSize());
}
TEST_F(DifferTest, MarkDirtyBlocks_All) {
InitDiffer(kScreenWidth, kScreenHeight);
ClearDiffInfo();
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
WriteBlockPixel(curr_.get(), x, y, 10, 10, 0xff00ff);
}
}
MarkDirtyBlocks(prev_.get(), curr_.get());
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
EXPECT_EQ(1, GetDiffInfo(x, y))
<< "when x = " << x << ", and y = " << y;
}
}
}
TEST_F(DifferTest, MarkDirtyBlocks_Sampling) {
InitDiffer(kScreenWidth, kScreenHeight);
ClearDiffInfo();
WriteBlockPixel(curr_.get(), 1, 0, 10, 10, 0xff00ff);
WriteBlockPixel(curr_.get(), 2, 1, 10, 10, 0xff00ff);
WriteBlockPixel(curr_.get(), 0, 2, 10, 10, 0xff00ff);
MarkDirtyBlocks(prev_.get(), curr_.get());
EXPECT_EQ(0, GetDiffInfo(0, 0));
EXPECT_EQ(0, GetDiffInfo(0, 1));
EXPECT_EQ(1, GetDiffInfo(0, 2));
EXPECT_EQ(1, GetDiffInfo(1, 0));
EXPECT_EQ(0, GetDiffInfo(1, 1));
EXPECT_EQ(0, GetDiffInfo(1, 2));
EXPECT_EQ(0, GetDiffInfo(2, 0));
EXPECT_EQ(1, GetDiffInfo(2, 1));
EXPECT_EQ(0, GetDiffInfo(2, 2));
}
TEST_F(DifferTest, DiffBlock) {
InitDiffer(kScreenWidth, kScreenHeight);
EXPECT_EQ(0, DiffBlock(0, 0));
EXPECT_EQ(0, DiffBlock(1, 1));
int max = kBlockSize - 1;
WriteBlockPixel(curr_.get(), 1, 1, 0, 0, 0xffffff);
WriteBlockPixel(curr_.get(), 1, 1, 0, max, 0xffffff);
WriteBlockPixel(curr_.get(), 1, 1, max, 0, 0xffffff);
WriteBlockPixel(curr_.get(), 1, 1, max, max, 0xffffff);
EXPECT_EQ(0, DiffBlock(0, 0));
EXPECT_EQ(0, DiffBlock(0, 1));
EXPECT_EQ(0, DiffBlock(0, 2));
EXPECT_EQ(0, DiffBlock(1, 0));
EXPECT_EQ(1, DiffBlock(1, 1));
EXPECT_EQ(0, DiffBlock(1, 2));
EXPECT_EQ(0, DiffBlock(2, 0));
EXPECT_EQ(0, DiffBlock(2, 1));
EXPECT_EQ(0, DiffBlock(2, 2));
}
TEST_F(DifferTest, Partial_Setup) {
InitDiffer(kPartialScreenWidth, kPartialScreenHeight);
EXPECT_EQ(4, GetDiffInfoWidth());
EXPECT_EQ(4, GetDiffInfoHeight());
EXPECT_EQ(16, GetDiffInfoSize());
}
TEST_F(DifferTest, Partial_FirstPixel) {
InitDiffer(kPartialScreenWidth, kPartialScreenHeight);
ClearDiffInfo();
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
WriteBlockPixel(curr_.get(), x, y, 0, 0, 0xff00ff);
}
}
MarkDirtyBlocks(prev_.get(), curr_.get());
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
EXPECT_EQ(1, GetDiffInfo(x, y))
<< "when x = " << x << ", and y = " << y;
}
}
}
TEST_F(DifferTest, Partial_BorderPixel) {
InitDiffer(kPartialScreenWidth, kPartialScreenHeight);
ClearDiffInfo();
for (int y = 0; y < height_; y++) {
WritePixel(curr_.get(), width_ - 1, y, 0xff00ff);
}
for (int x = 0; x < width_; x++) {
WritePixel(curr_.get(), x, height_ - 1, 0xff00ff);
}
MarkDirtyBlocks(prev_.get(), curr_.get());
int x_last = GetDiffInfoWidth() - 2;
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
EXPECT_EQ(1, GetDiffInfo(x_last, y))
<< "when x = " << x_last << ", and y = " << y;
}
int y_last = GetDiffInfoHeight() - 2;
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
EXPECT_EQ(1, GetDiffInfo(x, y_last))
<< "when x = " << x << ", and y = " << y_last;
}
for (int y = 0; y < GetDiffInfoHeight() - 2; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 2; x++) {
EXPECT_EQ(0, GetDiffInfo(x, y)) << "when x = " << x << ", and y = " << y;
}
}
}
TEST_F(DifferTest, MergeBlocks_Empty) {
InitDiffer(kScreenWidth, kScreenHeight);
ClearDiffInfo();
SkRegion dirty;
MergeBlocks(&dirty);
EXPECT_TRUE(dirty.isEmpty());
}
TEST_F(DifferTest, MergeBlocks_SingleBlock) {
InitDiffer(kScreenWidth, kScreenHeight);
for (int y = 0; y < GetDiffInfoHeight() - 1; y++) {
for (int x = 0; x < GetDiffInfoWidth() - 1; x++) {
ASSERT_TRUE(MarkBlocksAndCheckMerge(x, y, 1, 1)) << "x: " << x
<< "y: " << y;
}
}
}
TEST_F(DifferTest, MergeBlocks_BlockRow) {
InitDiffer(kScreenWidth, kScreenHeight);
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 2, 1));
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 1, 3, 1));
ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 2, 2, 1));
}
TEST_F(DifferTest, MergeBlocks_BlockColumn) {
InitDiffer(kScreenWidth, kScreenHeight);
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 1, 2));
ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 1, 1, 2));
ASSERT_TRUE(MarkBlocksAndCheckMerge(2, 0, 1, 3));
}
TEST_F(DifferTest, MergeBlocks_BlockRect) {
InitDiffer(kScreenWidth, kScreenHeight);
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 2, 2));
ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 1, 2, 2));
ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 0, 2, 3));
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 1, 3, 2));
ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 3, 3));
}
TEST_F(DifferTest, MergeBlocks_MultiRect) {
InitDiffer(kScreenWidth, kScreenHeight);
SkRegion dirty;
ClearDiffInfo();
MarkBlocks(1, 0, 1, 1);
MarkBlocks(0, 1, 1, 1);
MarkBlocks(2, 2, 1, 1);
dirty.setEmpty();
MergeBlocks(&dirty);
ASSERT_EQ(3, RegionRectCount(dirty));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 0, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 2, 1, 1));
ClearDiffInfo();
MarkBlocks(2, 0, 1, 1);
MarkBlocks(0, 1, 3, 2);
dirty.setEmpty();
MergeBlocks(&dirty);
ASSERT_EQ(2, RegionRectCount(dirty));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 0, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 3, 2));
ClearDiffInfo();
MarkBlocks(0, 1, 1, 1);
MarkBlocks(2, 1, 1, 1);
MarkBlocks(0, 2, 3, 1);
dirty.setEmpty();
MergeBlocks(&dirty);
ASSERT_EQ(3, RegionRectCount(dirty));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 1, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 2, 3, 1));
ClearDiffInfo();
MarkBlocks(0, 0, 3, 1);
MarkBlocks(0, 1, 1, 1);
MarkBlocks(2, 1, 1, 1);
MarkBlocks(0, 2, 3, 1);
dirty.setEmpty();
MergeBlocks(&dirty);
ASSERT_EQ(4, RegionRectCount(dirty));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 3, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 1, 1, 1));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 2, 3, 1));
ClearDiffInfo();
MarkBlocks(0, 0, 2, 2);
MarkBlocks(1, 2, 1, 1);
dirty.setEmpty();
MergeBlocks(&dirty);
ASSERT_EQ(2, RegionRectCount(dirty));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 2, 2));
ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 2, 1, 1));
}
}