diff --git a/api/video/i010_buffer.cc b/api/video/i010_buffer.cc index 71e59e656a..557a7694af 100644 --- a/api/video/i010_buffer.cc +++ b/api/video/i010_buffer.cc @@ -232,4 +232,33 @@ void I010Buffer::ScaleFrom(const I010BufferInterface& src) { 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 dc24c0ee03..8f354b66e5 100644 --- a/api/video/i010_buffer.h +++ b/api/video/i010_buffer.h @@ -65,6 +65,12 @@ class I010Buffer : public I010BufferInterface { // 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 f4c64864bd..c468f51f97 100644 --- a/api/video/i420_buffer.cc +++ b/api/video/i420_buffer.cc @@ -226,4 +226,33 @@ void I420Buffer::ScaleFrom(const I420BufferInterface& src) { 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 631e394e53..ca290ee70c 100644 --- a/api/video/i420_buffer.h +++ b/api/video/i420_buffer.h @@ -97,6 +97,12 @@ class RTC_EXPORT I420Buffer : public I420BufferInterface { // 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 a4b110b6b9..9d01339146 100644 --- a/common_video/video_frame_unittest.cc +++ b/common_video/video_frame_unittest.cc @@ -226,6 +226,47 @@ void CheckRotate(int width, } } +int GetU(rtc::scoped_refptr 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 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 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(canvas); + buf->PasteFrom(*picture.GetI420(), offset_col, offset_row); + } else { + I010Buffer* buf = static_cast(canvas); + buf->PasteFrom(*picture.GetI010(), offset_col, offset_row); + } +} + } // namespace TEST(TestVideoFrame, WidthHeightValues) { @@ -404,6 +445,43 @@ TEST_P(TestPlanarYuvBuffer, CropAndScale16x9) { 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 buf = + CreateGradient(GetParam(), kWidth, kHeight); + + rtc::scoped_refptr original = + CreateGradient(GetParam(), kWidth, kHeight); + + rtc::scoped_refptr picture = + CreateGradient(GetParam(), kPicSize, kPicSize); + + rtc::scoped_refptr 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,