Add ability to paste VideoFrameBuffer into the middle of I010Buffer and I420Buffer
Bug: webrtc:10152
Change-Id: I721136a3ba3604f0c685ef28637fb84fcf94778e
Reviewed-on: https://webrtc-review.googlesource.com/c/115300
Reviewed-by: Magnus Jedvert <magjed@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26089}
diff --git a/api/video/i010_buffer.cc b/api/video/i010_buffer.cc
index 71e59e6..557a769 100644
--- a/api/video/i010_buffer.cc
+++ b/api/video/i010_buffer.cc
@@ -232,4 +232,33 @@
CropAndScaleFrom(src, 0, 0, src.width(), src.height());
}
+void I010Buffer::PasteFrom(const I010BufferInterface& picture,
+ int offset_col,
+ int offset_row) {
+ RTC_CHECK_LE(picture.width() + offset_col, width());
+ RTC_CHECK_LE(picture.height() + offset_row, height());
+ RTC_CHECK_GE(offset_col, 0);
+ RTC_CHECK_GE(offset_row, 0);
+
+ // Pasted picture has to be aligned so subsumpled UV plane isn't corrupted.
+ RTC_CHECK(offset_col % 2 == 0);
+ RTC_CHECK(offset_row % 2 == 0);
+ RTC_CHECK(picture.width() % 2 == 0);
+ RTC_CHECK(picture.height() % 2 == 0);
+
+ libyuv::CopyPlane_16(picture.DataY(), picture.StrideY(),
+ MutableDataY() + StrideY() * offset_row + offset_col,
+ StrideY(), picture.width(), picture.height());
+
+ libyuv::CopyPlane_16(
+ picture.DataU(), picture.StrideU(),
+ MutableDataU() + StrideU() * offset_row / 2 + offset_col / 2, StrideU(),
+ picture.width() / 2, picture.height() / 2);
+
+ libyuv::CopyPlane_16(
+ picture.DataV(), picture.StrideV(),
+ MutableDataV() + StrideV() * offset_row / 2 + offset_col / 2, StrideV(),
+ picture.width() / 2, picture.height() / 2);
+}
+
} // namespace webrtc
diff --git a/api/video/i010_buffer.h b/api/video/i010_buffer.h
index dc24c0e..8f354b6 100644
--- a/api/video/i010_buffer.h
+++ b/api/video/i010_buffer.h
@@ -65,6 +65,12 @@
// Scale all of |src| to the size of |this| buffer, with no cropping.
void ScaleFrom(const I010BufferInterface& src);
+ // Pastes whole picture to canvas at (offset_row, offset_col).
+ // Offsets and picture dimensions must be even.
+ void PasteFrom(const I010BufferInterface& picture,
+ int offset_col,
+ int offset_row);
+
protected:
I010Buffer(int width, int height, int stride_y, int stride_u, int stride_v);
~I010Buffer() override;
diff --git a/api/video/i420_buffer.cc b/api/video/i420_buffer.cc
index f4c6486..c468f51 100644
--- a/api/video/i420_buffer.cc
+++ b/api/video/i420_buffer.cc
@@ -226,4 +226,33 @@
CropAndScaleFrom(src, 0, 0, src.width(), src.height());
}
+void I420Buffer::PasteFrom(const I420BufferInterface& picture,
+ int offset_col,
+ int offset_row) {
+ RTC_CHECK_LE(picture.width() + offset_col, width());
+ RTC_CHECK_LE(picture.height() + offset_row, height());
+ RTC_CHECK_GE(offset_col, 0);
+ RTC_CHECK_GE(offset_row, 0);
+
+ // Pasted picture has to be aligned so subsumpled UV plane isn't corrupted.
+ RTC_CHECK(offset_col % 2 == 0);
+ RTC_CHECK(offset_row % 2 == 0);
+ RTC_CHECK(picture.width() % 2 == 0);
+ RTC_CHECK(picture.height() % 2 == 0);
+
+ libyuv::CopyPlane(picture.DataY(), picture.StrideY(),
+ MutableDataY() + StrideY() * offset_row + offset_col,
+ StrideY(), picture.width(), picture.height());
+
+ libyuv::CopyPlane(
+ picture.DataU(), picture.StrideU(),
+ MutableDataU() + StrideU() * offset_row / 2 + offset_col / 2, StrideU(),
+ picture.width() / 2, picture.height() / 2);
+
+ libyuv::CopyPlane(
+ picture.DataV(), picture.StrideV(),
+ MutableDataV() + StrideV() * offset_row / 2 + offset_col / 2, StrideV(),
+ picture.width() / 2, picture.height() / 2);
+}
+
} // namespace webrtc
diff --git a/api/video/i420_buffer.h b/api/video/i420_buffer.h
index 631e394..ca290ee 100644
--- a/api/video/i420_buffer.h
+++ b/api/video/i420_buffer.h
@@ -97,6 +97,12 @@
// Scale all of |src| to the size of |this| buffer, with no cropping.
void ScaleFrom(const I420BufferInterface& src);
+ // Pastes whole picture to canvas at (offset_row, offset_col).
+ // Offsets and picture dimensions must be even.
+ void PasteFrom(const I420BufferInterface& picture,
+ int offset_col,
+ int offset_row);
+
protected:
I420Buffer(int width, int height);
I420Buffer(int width, int height, int stride_y, int stride_u, int stride_v);
diff --git a/common_video/video_frame_unittest.cc b/common_video/video_frame_unittest.cc
index a4b110b..9d01339 100644
--- a/common_video/video_frame_unittest.cc
+++ b/common_video/video_frame_unittest.cc
@@ -226,6 +226,47 @@
}
}
+int GetU(rtc::scoped_refptr<PlanarYuvBuffer> buf, int col, int row) {
+ if (buf->type() == VideoFrameBuffer::Type::kI420) {
+ return buf->GetI420()
+ ->DataU()[row / 2 * buf->GetI420()->StrideU() + col / 2];
+ } else {
+ return buf->GetI010()
+ ->DataU()[row / 2 * buf->GetI010()->StrideU() + col / 2];
+ }
+}
+
+int GetV(rtc::scoped_refptr<PlanarYuvBuffer> buf, int col, int row) {
+ if (buf->type() == VideoFrameBuffer::Type::kI420) {
+ return buf->GetI420()
+ ->DataV()[row / 2 * buf->GetI420()->StrideV() + col / 2];
+ } else {
+ return buf->GetI010()
+ ->DataV()[row / 2 * buf->GetI010()->StrideV() + col / 2];
+ }
+}
+
+int GetY(rtc::scoped_refptr<PlanarYuvBuffer> buf, int col, int row) {
+ if (buf->type() == VideoFrameBuffer::Type::kI420) {
+ return buf->GetI420()->DataY()[row * buf->GetI420()->StrideY() + col];
+ } else {
+ return buf->GetI010()->DataY()[row * buf->GetI010()->StrideY() + col];
+ }
+}
+
+void PasteFromBuffer(PlanarYuvBuffer* canvas,
+ const PlanarYuvBuffer& picture,
+ int offset_col,
+ int offset_row) {
+ if (canvas->type() == VideoFrameBuffer::Type::kI420) {
+ I420Buffer* buf = static_cast<I420Buffer*>(canvas);
+ buf->PasteFrom(*picture.GetI420(), offset_col, offset_row);
+ } else {
+ I010Buffer* buf = static_cast<I010Buffer*>(canvas);
+ buf->PasteFrom(*picture.GetI010(), offset_col, offset_row);
+ }
+}
+
} // namespace
TEST(TestVideoFrame, WidthHeightValues) {
@@ -404,6 +445,43 @@
CheckCrop(*scaled_buffer->ToI420(), 0.0, 0.125, 1.0, 0.75);
}
+TEST_P(TestPlanarYuvBuffer, PastesIntoBuffer) {
+ const int kOffsetx = 20;
+ const int kOffsety = 30;
+ const int kPicSize = 20;
+ const int kWidth = 160;
+ const int kHeight = 80;
+ rtc::scoped_refptr<PlanarYuvBuffer> buf =
+ CreateGradient(GetParam(), kWidth, kHeight);
+
+ rtc::scoped_refptr<PlanarYuvBuffer> original =
+ CreateGradient(GetParam(), kWidth, kHeight);
+
+ rtc::scoped_refptr<PlanarYuvBuffer> picture =
+ CreateGradient(GetParam(), kPicSize, kPicSize);
+
+ rtc::scoped_refptr<PlanarYuvBuffer> odd_picture =
+ CreateGradient(GetParam(), kPicSize + 1, kPicSize - 1);
+
+ PasteFromBuffer(buf.get(), *picture, kOffsetx, kOffsety);
+
+ for (int i = 0; i < kWidth; ++i) {
+ for (int j = 0; j < kHeight; ++j) {
+ bool is_inside = i >= kOffsetx && i < kOffsetx + kPicSize &&
+ j >= kOffsety && j < kOffsety + kPicSize;
+ if (!is_inside) {
+ EXPECT_EQ(GetU(original, i, j), GetU(buf, i, j));
+ EXPECT_EQ(GetV(original, i, j), GetV(buf, i, j));
+ EXPECT_EQ(GetY(original, i, j), GetY(buf, i, j));
+ } else {
+ EXPECT_EQ(GetU(picture, i - kOffsetx, j - kOffsety), GetU(buf, i, j));
+ EXPECT_EQ(GetV(picture, i - kOffsetx, j - kOffsety), GetV(buf, i, j));
+ EXPECT_EQ(GetY(picture, i - kOffsetx, j - kOffsety), GetY(buf, i, j));
+ }
+ }
+ }
+}
+
INSTANTIATE_TEST_CASE_P(,
TestPlanarYuvBuffer,
::testing::Values(VideoFrameBuffer::Type::kI420,