blob: fe94f73e5a8c11222bd9460cba19f529adc5af4b [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// Misc. common utility functions
//
// Author: Skal (pascal.massimino@gmail.com)
#include "src/utils/plane.h"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <numeric>
#include "src/dsp/dsp.h"
#include "src/dsp/math.h"
#include "src/utils/csp.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
//------------------------------------------------------------------------------
bool SamplingTaps::Taps::operator==(const SamplingTaps::Taps& rhs) const {
return radius == rhs.radius &&
std::equal(taps - radius, taps + radius + 1, rhs.taps - radius);
}
bool SamplingTaps::operator==(const SamplingTaps& rhs) const {
return t1 == rhs.t1 && t2 == rhs.t2;
}
bool SamplingTaps::Taps::IsValid() const {
return std::accumulate(taps - radius, taps + radius + 1, 0) == 16;
}
//------------------------------------------------------------------------------
// Lightly sharpen while downscaling.
// Centered on the top-left pixel for a 2x2 block.
static constexpr tap_t kDownSharpCoeffs[2 * 2 + 1] = {0, -2, 10, 10, -2};
const SamplingTaps SamplingTaps::kDownSharp = {
SamplingTaps::Taps(2, kDownSharpCoeffs + 2),
SamplingTaps::Taps(2, kDownSharpCoeffs + 2)};
// Just average a block of 2x2 pixels into a single pixel.
// Centered on the top-left pixel for a 2x2 block.
static constexpr tap_t kDownAvgCoeffs[2 * 1 + 1] = {0, 8, 8};
const SamplingTaps SamplingTaps::kDownAvg = {
SamplingTaps::Taps(1, kDownAvgCoeffs + 1),
SamplingTaps::Taps(1, kDownAvgCoeffs + 1)};
//------------------------------------------------------------------------------
// Slightly smooths while upscaling, should compensate for the sharp downscale.
static constexpr tap_t kUpSmoothFull[2 * 2 + 1] = {1, 3, 11, 1, 0};
static constexpr tap_t kUpSmoothHalf[2 * 2 + 1] = {0, 1, 11, 3, 1};
const SamplingTaps SamplingTaps::kUpSmooth = {
SamplingTaps::Taps(2, kUpSmoothFull + 2),
SamplingTaps::Taps(2, kUpSmoothHalf + 2)};
// Just scale by two with no interpolation.
static constexpr tap_t kUpNearestCoeffs[2 * 0 + 1] = {16};
const SamplingTaps SamplingTaps::kUpNearest = {
SamplingTaps::Taps(0, kUpNearestCoeffs),
SamplingTaps::Taps(0, kUpNearestCoeffs)};
//------------------------------------------------------------------------------
WP2Status PlaneBase::CheckRect(const Rectangle& rect) const {
WP2_CHECK_OK(rect.GetArea() > 0, WP2_STATUS_BAD_DIMENSION);
WP2_CHECK_OK(rect.x + rect.width <= w_, WP2_STATUS_BAD_DIMENSION);
WP2_CHECK_OK(rect.y + rect.height <= h_, WP2_STATUS_BAD_DIMENSION);
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
template <>
WP2Status Plane16::ToGray(WP2::ArgbBuffer* dst, BitDepth bit_depth) const {
assert(dst->format() == WP2_Argb_32);
const uint32_t H = std::min(h_, dst->height());
const uint32_t W = std::min(w_, dst->width());
for (uint32_t y = 0; y < H; ++y) {
uint8_t* const dst_y = dst->GetRow8(y);
const int16_t* const src_y = Row(y);
for (uint32_t x = 0; x < W; ++x) {
dst_y[4 * x + 0] = 0xff;
dst_y[4 * x + 1] = dst_y[4 * x + 2] = dst_y[4 * x + 3] = (uint8_t)Clamp(
ChangePrecision(src_y[x] - bit_depth.min(), bit_depth.num_bits, 8), 0,
255);
}
}
return WP2_STATUS_OK;
}
template <>
WP2Status Planef::ToGray(WP2::ArgbBuffer* dst, BitDepth bit_depth) const {
const uint32_t H = std::min(h_, dst->height());
const uint32_t W = std::min(w_, dst->width());
const uint32_t num_bits = bit_depth.num_bits + (bit_depth.is_signed ? 1 : 0);
const float scale =
(num_bits >= 8) ? 1.f / (1 << (num_bits - 8)) : (1 << (8 - num_bits));
const float offset = bit_depth.is_signed ? 128.f : 0.f;
for (uint32_t y = 0; y < H; ++y) {
uint8_t* const dst_y = dst->GetRow8(y);
const float* const src_y = Row(y);
for (uint32_t x = 0; x < W; ++x) {
dst_y[4 * x + 0] = 0xff;
dst_y[4 * x + 1] = dst_y[4 * x + 2] = dst_y[4 * x + 3] =
(uint8_t)Clamp(src_y[x] * scale + offset, 0.f, 255.f);
}
}
return WP2_STATUS_OK;
}
template <>
WP2Status Plane8u::ToGray(WP2::ArgbBuffer* dst, BitDepth bit_depth) const {
assert(bit_depth.num_bits <= 8 && !bit_depth.is_signed);
const uint32_t h = std::min(h_, dst->height());
const uint32_t w = std::min(w_, dst->width());
for (uint32_t y = 0; y < h; ++y) {
uint8_t* const dst_y = dst->GetRow8(y);
const uint8_t* const src_y = Row(y);
for (uint32_t x = 0; x < w; ++x) {
dst_y[4 * x + 0] = 0xff;
dst_y[4 * x + 1] = dst_y[4 * x + 2] = dst_y[4 * x + 3] =
(src_y[x] << (8 - bit_depth.num_bits));
}
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
template <>
WP2Status Plane16::GetSSE(const Plane16& src, uint64_t* const score) const {
WP2_CHECK_OK(w_ == src.w_ && h_ == src.h_, WP2_STATUS_BAD_DIMENSION);
WP2PSNRInit();
*score = 0;
for (uint32_t y = 0; y < h_; ++y) {
*score += WP2SumSquaredError16s(Row(y), src.Row(y), w_);
}
return WP2_STATUS_OK;
}
template <>
WP2Status Plane8u::GetSSE(const Plane8u& src, uint64_t* const score) const {
WP2_CHECK_OK(w_ == src.w_ && h_ == src.h_, WP2_STATUS_BAD_DIMENSION);
WP2PSNRInit();
*score = 0;
for (uint32_t y = 0; y < h_; ++y) {
*score += WP2SumSquaredError8u(Row(y), src.Row(y), w_);
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
bool YUVPlane::IsEmpty() const {
assert(U.IsEmpty() == Y.IsEmpty() && V.IsEmpty() == Y.IsEmpty());
if (Y.IsEmpty()) assert(A.IsEmpty());
return Y.IsEmpty();
}
bool YUVPlane::IsView() const {
if (IsEmpty()) return false;
assert(U.IsView() == Y.IsView() && V.IsView() == Y.IsView());
if (!A.IsEmpty()) assert(A.IsView() == Y.IsView());
return Y.IsView();
}
uint32_t YUVPlane::GetWidth() const {
assert(U.w_ == Y.w_ || U.w_ == DivCeil(Y.w_, 2u)); // 4:4:4 or 4:2:0
assert(V.w_ == U.w_);
if (!A.IsEmpty()) assert(A.w_ == Y.w_);
return Y.w_;
}
uint32_t YUVPlane::GetHeight() const {
assert(U.h_ == Y.h_ || U.h_ == DivCeil(Y.h_, 2u)); // 4:4:4 or 4:2:0
assert(V.h_ == U.h_);
if (!A.IsEmpty()) assert(A.h_ == Y.h_);
return Y.h_;
}
void YUVPlane::Clear() {
A.Clear();
Y.Clear();
U.Clear();
V.Clear();
}
const Plane16& YUVPlane::GetChannel(Channel channel) const {
if (channel == kYChannel) return Y;
if (channel == kUChannel) return U;
if (channel == kVChannel) return V;
assert(channel == kAChannel);
return A;
}
Plane16& YUVPlane::GetChannel(Channel channel) {
if (channel == kYChannel) return Y;
if (channel == kUChannel) return U;
if (channel == kVChannel) return V;
assert(channel == kAChannel);
return A;
}
//------------------------------------------------------------------------------
WP2Status YUVPlane::Resize(uint32_t width, uint32_t height, uint32_t pad,
bool has_alpha, bool as_yuv420) {
WP2_CHECK_OK(pad > 0, WP2_STATUS_INVALID_PARAMETER);
const uint32_t w = Pad(width, pad);
const uint32_t h = Pad(height, pad);
const uint32_t uv_w = as_yuv420 ? Pad((width + 1u) >> 1, pad) : w;
const uint32_t uv_h = as_yuv420 ? Pad((height + 1u) >> 1, pad) : h;
WP2_CHECK_STATUS(GetChannel(kYChannel).Resize(w, h));
WP2_CHECK_STATUS(GetChannel(kUChannel).Resize(uv_w, uv_h));
WP2_CHECK_STATUS(GetChannel(kVChannel).Resize(uv_w, uv_h));
if (has_alpha) {
WP2_CHECK_STATUS(GetChannel(kAChannel).Resize(w, h));
} else {
GetChannel(kAChannel).Clear();
}
return WP2_STATUS_OK;
}
WP2Status YUVPlane::Copy(const YUVPlane& src, bool resize_if_needed) {
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
WP2_CHECK_STATUS(
GetChannel(channel).Copy(src.GetChannel(channel), resize_if_needed));
}
return WP2_STATUS_OK;
}
void YUVPlane::Fill(const Rectangle& rect, const Ayuv38b& color) {
if (!A.IsEmpty()) A.Fill(rect, color.a);
Y.Fill(rect, color.y);
U.Fill(rect, color.u);
V.Fill(rect, color.v);
}
void YUVPlane::Fill(const Ayuv38b& color) {
if (!A.IsEmpty()) A.Fill(color.a);
Y.Fill(color.y);
U.Fill(color.u);
V.Fill(color.v);
}
WP2Status YUVPlane::FillPad(uint32_t non_padded_width,
uint32_t non_padded_height) {
WP2_CHECK_OK(!IsDownsampled(), WP2_STATUS_INVALID_PARAMETER);
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (channel == kAChannel && A.IsEmpty()) continue;
WP2_CHECK_STATUS(
GetChannel(channel).FillPad(non_padded_width, non_padded_height));
}
return WP2_STATUS_OK;
}
WP2Status YUVPlane::SetView(const YUVPlane& src, const Rectangle& rect) {
WP2_CHECK_OK(!IsDownsampled(), WP2_STATUS_INVALID_PARAMETER);
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (channel == kAChannel && src.A.IsEmpty()) {
A.Clear();
} else {
WP2_CHECK_STATUS(
GetChannel(channel).SetView(src.GetChannel(channel), rect));
}
}
return WP2_STATUS_OK;
}
WP2Status YUVPlane::SetView(const YUVPlane& src) {
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (channel == kAChannel && src.A.IsEmpty()) {
A.Clear();
} else {
WP2_CHECK_STATUS(GetChannel(channel).SetView(src.GetChannel(channel)));
}
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
// One-liners.
static WP2Status ResizeIfAllowed(uint32_t width, uint32_t height, uint32_t pad,
bool has_alpha, bool allowed,
YUVPlane* const buffer) {
if (allowed) {
WP2_CHECK_STATUS(buffer->Resize(width, height, pad, has_alpha));
} else {
WP2_CHECK_OK(pad > 0, WP2_STATUS_INVALID_PARAMETER);
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
Plane16* const plane = &buffer->GetChannel(channel);
if (channel == kAChannel && !has_alpha) {
WP2_CHECK_OK(plane->IsEmpty(), WP2_STATUS_BAD_DIMENSION);
} else {
WP2_CHECK_OK(plane->w_ == Pad(width, pad), WP2_STATUS_BAD_DIMENSION);
WP2_CHECK_OK(plane->h_ == Pad(height, pad), WP2_STATUS_BAD_DIMENSION);
}
}
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status YUVPlane::Import(const ArgbBuffer& rgb, bool import_alpha,
const CSPTransform& csp_transform,
bool resize_if_needed, size_t pad) {
WP2_CHECK_OK(rgb.format() == WP2_Argb_32 || rgb.format() == WP2_ARGB_32 ||
rgb.format() == WP2_Argb_38,
WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_STATUS(ResizeIfAllowed(rgb.width(), rgb.height(), pad, import_alpha,
resize_if_needed, this));
const bool premultiply = !WP2IsPremultiplied(rgb.format()) && import_alpha;
if (WP2Formatbpc(rgb.format()) == 8) {
assert(WP2FormatBpp(rgb.format()) == 4); // 4 expected channels.
for (size_t y = 0; y < rgb.height(); ++y) {
int16_t* const y_row = Y.Row(y);
int16_t* const u_row = U.Row(y);
int16_t* const v_row = V.Row(y);
int16_t* const a_row = import_alpha ? A.Row(y) : nullptr;
const uint8_t* rgb_row = rgb.GetRow8(y);
for (size_t x = 0; x < rgb.width(); ++x, rgb_row += 4) {
// Do not discard alpha by accident.
if (rgb_row[0] != kAlphaMax) assert(import_alpha);
int32_t r = rgb_row[1], g = rgb_row[2], b = rgb_row[3];
const uint8_t a = rgb_row[0];
if (premultiply && a != kAlphaMax) {
r = WP2::DivBy255(r * a);
g = WP2::DivBy255(g * a);
b = WP2::DivBy255(b * a);
}
csp_transform.Rgb8ToYuv(r, g, b, &y_row[x], &u_row[x], &v_row[x]);
if (a_row != nullptr) a_row[x] = a;
}
}
} else {
assert(WP2FormatBpp(rgb.format()) == 8); // 4 expected channels.
assert(!premultiply); // There is no ARGB_38 for now.
for (size_t y = 0; y < rgb.height(); ++y) {
int16_t* const y_row = Y.Row(y);
int16_t* const u_row = U.Row(y);
int16_t* const v_row = V.Row(y);
int16_t* const a_row = import_alpha ? A.Row(y) : nullptr;
const uint16_t* rgb_row = rgb.GetRow16(y);
for (size_t x = 0; x < rgb.width(); ++x, rgb_row += 4) {
// Do not discard alpha by accident.
if (rgb_row[0] != kAlphaMax) assert(import_alpha);
csp_transform.Rgb10ToYuv(rgb_row[1], rgb_row[2], rgb_row[3], &y_row[x],
&u_row[x], &v_row[x]);
if (a_row != nullptr) a_row[x] = rgb_row[0];
}
}
}
WP2_CHECK_STATUS(FillPad(rgb.width(), rgb.height()));
return WP2_STATUS_OK;
}
WP2Status YUVPlane::Import(const ArgbBuffer& rgb, bool import_alpha,
const CSPMtx& rgb_to_ccsp, bool resize_if_needed,
size_t pad) {
WP2_CHECK_OK(rgb.format() == WP2_Argb_32, WP2_STATUS_INVALID_PARAMETER);
int16_t m[9];
for (uint32_t i = 0; i < 9; ++i) {
m[i] = ChangePrecision(rgb_to_ccsp.matrix[i], rgb_to_ccsp.shift,
CSPTransform::kMtxShift);
WP2_CHECK_OK(std::abs(m[i]) < (1 << CSPTransform::kMtxBits),
WP2_STATUS_INVALID_PARAMETER);
}
WP2_CHECK_STATUS(ResizeIfAllowed(rgb.width(), rgb.height(), pad, import_alpha,
resize_if_needed, this));
for (size_t y = 0; y < rgb.height(); ++y) {
int16_t* const y_row = Y.Row(y);
int16_t* const u_row = U.Row(y);
int16_t* const v_row = V.Row(y);
int16_t* const a_row = import_alpha ? A.Row(y) : nullptr;
const uint8_t* const rgb_row = rgb.GetRow8(y);
for (size_t x = 0; x < rgb.width(); ++x) {
const uint32_t rgb_x = WP2FormatBpp(rgb.format()) * x;
if (import_alpha) a_row[x] = rgb_row[rgb_x + 0];
Multiply(rgb_row[rgb_x + 1], rgb_row[rgb_x + 2], rgb_row[rgb_x + 3], m,
CSPTransform::kMtxShift, &y_row[x], &u_row[x], &v_row[x]);
}
}
WP2_CHECK_STATUS(FillPad(rgb.width(), rgb.height()));
return WP2_STATUS_OK;
}
WP2Status YUVPlane::Import(const YUVPlane& ccsp, const CSPMtx& ccsp_to_rgb,
const CSPTransform& csp_transform,
bool resize_if_needed, size_t pad) {
WP2_CHECK_OK(!ccsp.IsDownsampled(), WP2_STATUS_INVALID_PARAMETER);
const uint32_t width = ccsp.GetWidth(), height = ccsp.GetHeight();
WP2_CHECK_STATUS(ResizeIfAllowed(width, height, pad, ccsp.HasAlpha(),
resize_if_needed, this));
WP2_CHECK_STATUS(csp_transform.CustomToYuv(
width, height, ccsp.Y.Row(0), ccsp.Y.Step(), ccsp.U.Row(0), ccsp.U.Step(),
ccsp.V.Row(0), ccsp.V.Step(), ccsp_to_rgb, Y.Row(0), Y.Step(), U.Row(0),
U.Step(), V.Row(0), V.Step()));
if (!ccsp.A.IsEmpty()) {
Plane16 non_padded_alpha;
WP2_CHECK_STATUS(non_padded_alpha.SetView(A, {0, 0, width, height}));
WP2_CHECK_STATUS(non_padded_alpha.Copy(ccsp.A, /*resize_if_needed=*/false));
}
WP2_CHECK_STATUS(FillPad(width, height));
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status YUVPlane::Export(const CSPTransform& transform, bool resize_if_needed,
ArgbBuffer* const dst,
const SamplingTaps* const upsample_if_needed) const {
WP2_CHECK_OK(dst->format() == WP2_Argb_32 || dst->format() == WP2_ARGB_32 ||
dst->format() == WP2_Argb_38,
WP2_STATUS_INVALID_PARAMETER);
if (IsDownsampled()) {
WP2_CHECK_OK(upsample_if_needed != nullptr, WP2_STATUS_INVALID_COLORSPACE);
YUVPlane upsampled;
WP2_CHECK_STATUS(upsampled.UpsampleFrom(*this, *upsample_if_needed,
/*use_views_for_ya=*/true));
WP2_CHECK_STATUS(upsampled.Export(transform, resize_if_needed, dst));
return WP2_STATUS_OK;
}
if (dst->width() != GetWidth() || dst->height() != GetHeight()) {
WP2_CHECK_OK(resize_if_needed, WP2_STATUS_BAD_DIMENSION);
WP2_CHECK_STATUS(dst->Resize(GetWidth(), GetHeight()));
}
const int16_t* const rgb_average = (WP2Formatbpc(dst->format()) == 8)
? transform.GetRgb8Average()
: transform.GetRgb10Average();
WP2CSPConverterInit();
if (HasAlpha()) {
WP2AyuvToArgbFunc cvrt_func =
(dst->format() == WP2_Argb_32) ? WP2AyuvToArgb32
: (dst->format() == WP2_ARGB_32) ? WP2AyuvToARGB32
: WP2AyuvToArgb38;
for (size_t y = 0; y < dst->height(); ++y) {
cvrt_func(Y.Row(y), U.Row(y), V.Row(y), A.Row(y), rgb_average,
transform.GetYuvToRgbMatrix(), dst->GetRow(y), dst->width());
}
} else {
// No alpha: premultiplying has no impact, so Argb/ARGB do not matter.
WP2YuvToArgbFunc cvrt_func =
(dst->format() == WP2_Argb_32 || dst->format() == WP2_ARGB_32)
? WP2YuvToArgb32
: WP2YuvToArgb38;
for (size_t y = 0; y < dst->height(); ++y) {
cvrt_func(Y.Row(y), U.Row(y), V.Row(y), rgb_average,
transform.GetYuvToRgbMatrix(), dst->GetRow(y), dst->width());
}
}
return WP2_STATUS_OK;
}
WP2Status YUVPlane::Export(const CSPMtx& ccsp_to_rgb, bool resize_if_needed,
ArgbBuffer* const dst,
const SamplingTaps* const upsample_if_needed) const {
WP2_CHECK_OK(dst->format() == WP2_Argb_32, WP2_STATUS_INVALID_PARAMETER);
if (IsDownsampled()) {
WP2_CHECK_OK(upsample_if_needed != nullptr, WP2_STATUS_INVALID_COLORSPACE);
YUVPlane upsampled;
WP2_CHECK_STATUS(upsampled.UpsampleFrom(*this, *upsample_if_needed,
/*use_views_for_ya=*/true));
WP2_CHECK_STATUS(upsampled.Export(ccsp_to_rgb, resize_if_needed, dst));
return WP2_STATUS_OK;
}
if (dst->width() != GetWidth() || dst->height() != GetHeight()) {
WP2_CHECK_OK(resize_if_needed, WP2_STATUS_BAD_DIMENSION);
WP2_CHECK_STATUS(dst->Resize(GetWidth(), GetHeight()));
}
WP2_CHECK_STATUS(CSPTransform::CustomToArgb(
dst->width(), dst->height(), Y.Row(0), Y.Step(), U.Row(0), U.Step(),
V.Row(0), V.Step(), HasAlpha() ? A.Row(0) : nullptr, A.Step(),
ccsp_to_rgb, dst));
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status YUVPlane::DownsampleFrom(const YUVPlane& src, const SamplingTaps& f,
bool use_views_for_ya) {
WP2_CHECK_OK(!src.IsDownsampled(), WP2_STATUS_INVALID_PARAMETER);
if (use_views_for_ya) {
WP2_CHECK_STATUS(Y.SetView(src.Y));
WP2_CHECK_STATUS(A.SetView(src.A));
} else {
WP2_CHECK_STATUS(Y.Copy(src.Y, /*resize_if_needed=*/true));
WP2_CHECK_STATUS(A.Copy(src.A, /*resize_if_needed=*/true));
}
WP2_CHECK_STATUS(U.DownsampleFrom(src.U, f));
WP2_CHECK_STATUS(V.DownsampleFrom(src.V, f));
return WP2_STATUS_OK;
}
WP2Status YUVPlane::Downsample(const SamplingTaps& f) {
WP2_CHECK_OK(!IsDownsampled(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_STATUS(U.Downsample(f));
WP2_CHECK_STATUS(V.Downsample(f));
return WP2_STATUS_OK;
}
WP2Status YUVPlane::UpsampleFrom(const YUVPlane& src, const SamplingTaps& f,
bool use_views_for_ya) {
if (src.GetWidth() == 1 && src.GetHeight() == 1) {
if (use_views_for_ya) return SetView(src);
return Copy(src, /*resize_if_needed=*/true);
}
WP2_CHECK_OK(src.IsDownsampled(), WP2_STATUS_INVALID_PARAMETER);
if (use_views_for_ya) {
WP2_CHECK_STATUS(Y.SetView(src.Y));
if (src.HasAlpha()) {
WP2_CHECK_STATUS(A.SetView(src.A));
} else {
A.Clear();
}
} else {
WP2_CHECK_STATUS(Y.Copy(src.Y, /*resize_if_needed=*/true));
WP2_CHECK_STATUS(A.Copy(src.A, /*resize_if_needed=*/true));
}
WP2_CHECK_STATUS(U.UpsampleFrom(src.U, Y.w_, Y.h_, f));
WP2_CHECK_STATUS(V.UpsampleFrom(src.V, Y.w_, Y.h_, f));
return WP2_STATUS_OK;
}
WP2Status YUVPlane::Upsample(const SamplingTaps& f) {
if (GetWidth() == 1 && GetHeight() == 1) return WP2_STATUS_OK;
WP2_CHECK_OK(IsDownsampled(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_STATUS(U.Upsample(Y.w_, Y.h_, f));
WP2_CHECK_STATUS(V.Upsample(Y.w_, Y.h_, f));
return WP2_STATUS_OK;
}
bool YUVPlane::IsDownsampled() const {
// GetWidth() and GetHeight() already verify the dimensions of the planes.
return (U.w_ != GetWidth() || U.h_ != GetHeight());
}
// Checks that Y, U, V and A planes (if present) are all the same size.
static WP2Status CheckPlanesSameSize(const YUVPlane& yuv) {
WP2_CHECK_OK(yuv.Y.IsSameSize(yuv.U), WP2_STATUS_BAD_DIMENSION);
WP2_CHECK_OK(yuv.Y.IsSameSize(yuv.V), WP2_STATUS_BAD_DIMENSION);
if (yuv.HasAlpha()) {
WP2_CHECK_OK(yuv.Y.IsSameSize(yuv.A), WP2_STATUS_BAD_DIMENSION);
}
return WP2_STATUS_OK;
}
static WP2Status Composite(const YUVPlane& background,
const YUVPlane& foreground,
const CSPTransform& transform, YUVPlane* const dst) {
WP2_CHECK_STATUS(CheckPlanesSameSize(background));
WP2_CHECK_STATUS(CheckPlanesSameSize(foreground));
WP2_CHECK_OK(background.Y.IsSameSize(foreground.Y), WP2_STATUS_BAD_DIMENSION);
const Ayuv38b shift = transform.ToYuv({255, 0, 0, 0});
for (uint32_t y = 0; y < foreground.Y.h_; ++y) {
int16_t* const dst_row[4] = {dst->Y.Row(y), dst->U.Row(y), dst->V.Row(y),
dst->A.Row(y)};
const int16_t* const fg_row[4] = {foreground.Y.Row(y), foreground.U.Row(y),
foreground.V.Row(y), foreground.A.Row(y)};
const int16_t* const bg_row[4] = {background.Y.Row(y), background.U.Row(y),
background.V.Row(y), background.A.Row(y)};
for (uint32_t x = 0; x < foreground.Y.w_; ++x) {
const uint8_t b = kAlphaMax - fg_row[kAChannel][x];
dst_row[kYChannel][x] =
fg_row[kYChannel][x] + DivBy255(b * (bg_row[kYChannel][x] - shift.y));
dst_row[kUChannel][x] =
fg_row[kUChannel][x] + DivBy255(b * (bg_row[kUChannel][x] - shift.u));
dst_row[kVChannel][x] =
fg_row[kVChannel][x] + DivBy255(b * (bg_row[kVChannel][x] - shift.v));
dst_row[kAChannel][x] =
fg_row[kAChannel][x] + DivBy255(b * bg_row[kAChannel][x]);
}
}
return WP2_STATUS_OK;
}
WP2Status YUVPlane::CompositeOver(const YUVPlane& background,
const CSPTransform& transform) {
if (!HasAlpha()) return WP2_STATUS_OK;
return Composite(background, *this, transform, this);
}
WP2Status YUVPlane::CompositeUnder(const YUVPlane& foreground,
const CSPTransform& transform) {
if (!foreground.HasAlpha()) {
return Copy(foreground, /*resize_if_needed=*/false);
}
return Composite(*this, foreground, transform, this);
}
bool YUVPlane::IsMonochrome() const {
const int16_t* u_row = U.Row(0);
const int16_t* v_row = V.Row(0);
const size_t u_step = U.step_;
const size_t v_step = V.step_;
for (uint32_t y = 0; y < U.h_; ++y) {
if (std::find_if(u_row, u_row + U.w_, [](int16_t v) { return v != 0; }) !=
u_row + U.w_) {
return false;
}
if (std::find_if(v_row, v_row + V.w_, [](int16_t v) { return v != 0; }) !=
v_row + V.w_) {
return false;
}
u_row += u_step;
v_row += v_step;
}
return true;
}
//------------------------------------------------------------------------------
void swap(YUVPlane& a, YUVPlane& b) {
using std::swap;
swap(a.Y, b.Y);
swap(a.U, b.U);
swap(a.V, b.V);
swap(a.A, b.A);
}
//------------------------------------------------------------------------------
} // namespace WP2