blob: 74b3a84c50806ad70ba4e81b5abe3f8015fc47e0 [file] [log] [blame]
/*
* Copyright 2020 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkYUVAInfo.h"
#include "src/core/SkSafeMath.h"
#include "src/core/SkYUVAInfoLocation.h"
#include <algorithm>
static bool is_plane_config_compatible_with_subsampling(SkYUVAInfo::PlaneConfig config,
SkYUVAInfo::Subsampling subsampling) {
if (config == SkYUVAInfo::PlaneConfig::kUnknown ||
subsampling == SkYUVAInfo::Subsampling::kUnknown) {
return false;
}
return subsampling == SkYUVAInfo::Subsampling::k444 ||
(config != SkYUVAInfo::PlaneConfig::kYUV &&
config != SkYUVAInfo::PlaneConfig::kYUVA &&
config != SkYUVAInfo::PlaneConfig::kUYV &&
config != SkYUVAInfo::PlaneConfig::kUYVA);
}
std::tuple<int, int> SkYUVAInfo::SubsamplingFactors(Subsampling subsampling) {
switch (subsampling) {
case Subsampling::kUnknown: return {0, 0};
case Subsampling::k444: return {1, 1};
case Subsampling::k422: return {2, 1};
case Subsampling::k420: return {2, 2};
case Subsampling::k440: return {1, 2};
case Subsampling::k411: return {4, 1};
case Subsampling::k410: return {4, 2};
}
SkUNREACHABLE;
}
std::tuple<int, int> SkYUVAInfo::PlaneSubsamplingFactors(PlaneConfig planeConfig,
Subsampling subsampling,
int planeIdx) {
if (!is_plane_config_compatible_with_subsampling(planeConfig, subsampling) ||
planeIdx < 0 ||
planeIdx > NumPlanes(planeConfig)) {
return {0, 0};
}
bool isSubsampledPlane = false;
switch (planeConfig) {
case PlaneConfig::kUnknown: SkUNREACHABLE;
case PlaneConfig::kY_U_V:
case PlaneConfig::kY_V_U:
case PlaneConfig::kY_U_V_A:
case PlaneConfig::kY_V_U_A:
isSubsampledPlane = planeIdx == 1 || planeIdx == 2;
break;
case PlaneConfig::kY_UV:
case PlaneConfig::kY_VU:
case PlaneConfig::kY_UV_A:
case PlaneConfig::kY_VU_A:
isSubsampledPlane = planeIdx == 1;
break;
case PlaneConfig::kYUV:
case PlaneConfig::kUYV:
case PlaneConfig::kYUVA:
case PlaneConfig::kUYVA:
break;
}
return isSubsampledPlane ? SubsamplingFactors(subsampling) : std::make_tuple(1, 1);
}
int SkYUVAInfo::PlaneDimensions(SkISize imageDimensions,
PlaneConfig planeConfig,
Subsampling subsampling,
SkEncodedOrigin origin,
SkISize planeDimensions[SkYUVAInfo::kMaxPlanes]) {
std::fill_n(planeDimensions, SkYUVAInfo::kMaxPlanes, SkISize{0, 0});
if (!is_plane_config_compatible_with_subsampling(planeConfig, subsampling)) {
return 0;
}
int w = imageDimensions.width();
int h = imageDimensions.height();
if (origin >= kLeftTop_SkEncodedOrigin) {
using std::swap;
swap(w, h);
}
auto down2 = [](int x) { return (x + 1)/2; };
auto down4 = [](int x) { return (x + 3)/4; };
SkISize uvSize;
switch (subsampling) {
case Subsampling::kUnknown: SkUNREACHABLE;
case Subsampling::k444: uvSize = { w , h }; break;
case Subsampling::k422: uvSize = {down2(w), h }; break;
case Subsampling::k420: uvSize = {down2(w), down2(h)}; break;
case Subsampling::k440: uvSize = { w , down2(h)}; break;
case Subsampling::k411: uvSize = {down4(w), h }; break;
case Subsampling::k410: uvSize = {down4(w), down2(h)}; break;
}
switch (planeConfig) {
case PlaneConfig::kUnknown: SkUNREACHABLE;
case PlaneConfig::kY_U_V:
case PlaneConfig::kY_V_U:
planeDimensions[0] = {w, h};
planeDimensions[1] = planeDimensions[2] = uvSize;
return 3;
case PlaneConfig::kY_UV:
case PlaneConfig::kY_VU:
planeDimensions[0] = {w, h};
planeDimensions[1] = uvSize;
return 2;
case PlaneConfig::kY_U_V_A:
case PlaneConfig::kY_V_U_A:
planeDimensions[0] = planeDimensions[3] = {w, h};
planeDimensions[1] = planeDimensions[2] = uvSize;
return 4;
case PlaneConfig::kY_UV_A:
case PlaneConfig::kY_VU_A:
planeDimensions[0] = planeDimensions[2] = {w, h};
planeDimensions[1] = uvSize;
return 3;
case PlaneConfig::kYUV:
case PlaneConfig::kUYV:
case PlaneConfig::kYUVA:
case PlaneConfig::kUYVA:
planeDimensions[0] = {w, h};
SkASSERT(planeDimensions[0] == uvSize);
return 1;
}
SkUNREACHABLE;
}
static bool channel_index_to_channel(uint32_t channelFlags,
int channelIdx,
SkColorChannel* channel) {
switch (channelFlags) {
case kGray_SkColorChannelFlag: // For gray returning any of R, G, or B for index 0 is ok.
case kRed_SkColorChannelFlag:
if (channelIdx == 0) {
*channel = SkColorChannel::kR;
return true;
}
return false;
case kGrayAlpha_SkColorChannelFlags:
switch (channelIdx) {
case 0: *channel = SkColorChannel::kR; return true;
case 1: *channel = SkColorChannel::kA; return true;
default: return false;
}
case kAlpha_SkColorChannelFlag:
if (channelIdx == 0) {
*channel = SkColorChannel::kA;
return true;
}
return false;
case kRG_SkColorChannelFlags:
if (channelIdx == 0 || channelIdx == 1) {
*channel = static_cast<SkColorChannel>(channelIdx);
return true;
}
return false;
case kRGB_SkColorChannelFlags:
if (channelIdx >= 0 && channelIdx <= 2) {
*channel = static_cast<SkColorChannel>(channelIdx);
return true;
}
return false;
case kRGBA_SkColorChannelFlags:
if (channelIdx >= 0 && channelIdx <= 3) {
*channel = static_cast<SkColorChannel>(channelIdx);
return true;
}
return false;
default:
return false;
}
}
SkYUVAInfo::YUVALocations SkYUVAInfo::GetYUVALocations(PlaneConfig config,
const uint32_t* planeChannelFlags) {
// Like YUVALocation but chanIdx refers to channels by index rather than absolute channel, e.g.
// A is the 0th channel of an alpha-only texture. We'll use this plus planeChannelFlags to get
// the actual channel.
struct PlaneAndIndex {int plane, chanIdx;};
const PlaneAndIndex* planesAndIndices = nullptr;
switch (config) {
case PlaneConfig::kUnknown:
return {};
case PlaneConfig::kY_U_V: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 0}, {2, 0}, {-1, -1}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kY_V_U: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {2, 0}, {1, 0}, {-1, -1}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kY_UV: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 0}, {1, 1}, {-1, -1}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kY_VU: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 1}, {1, 0}, {-1, -1}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kYUV: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {0, 1}, {0, 2}, {-1, -1}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kUYV: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 1}, {0, 0}, {0, 2}, {-1, -1}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kY_U_V_A: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 0}, {2, 0}, {3, 0}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kY_V_U_A: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {2, 0}, {1, 0}, {3, 0}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kY_UV_A: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 0}, {1, 1}, {2, 0}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kY_VU_A: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 1}, {1, 0}, {2, 0}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kYUVA: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {0, 1}, {0, 2}, {0, 3}};
planesAndIndices = kPlanesAndIndices;
break;
}
case PlaneConfig::kUYVA: {
static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 1}, {0, 0}, {0, 2}, {0, 3}};
planesAndIndices = kPlanesAndIndices;
break;
}
}
SkASSERT(planesAndIndices);
YUVALocations yuvaLocations;
for (int i = 0; i < SkYUVAInfo::kYUVAChannelCount; ++i) {
auto [plane, chanIdx] = planesAndIndices[i];
SkColorChannel channel;
if (plane >= 0) {
if (!channel_index_to_channel(planeChannelFlags[plane], chanIdx, &channel)) {
return {};
}
yuvaLocations[i] = {plane, channel};
} else {
SkASSERT(i == 3);
yuvaLocations[i] = {-1, SkColorChannel::kR};
}
}
return yuvaLocations;
}
bool SkYUVAInfo::HasAlpha(PlaneConfig planeConfig) {
switch (planeConfig) {
case PlaneConfig::kUnknown: return false;
case PlaneConfig::kY_U_V: return false;
case PlaneConfig::kY_V_U: return false;
case PlaneConfig::kY_UV: return false;
case PlaneConfig::kY_VU: return false;
case PlaneConfig::kYUV: return false;
case PlaneConfig::kUYV: return false;
case PlaneConfig::kY_U_V_A: return true;
case PlaneConfig::kY_V_U_A: return true;
case PlaneConfig::kY_UV_A: return true;
case PlaneConfig::kY_VU_A: return true;
case PlaneConfig::kYUVA: return true;
case PlaneConfig::kUYVA: return true;
}
SkUNREACHABLE;
}
SkYUVAInfo::SkYUVAInfo(SkISize dimensions,
PlaneConfig planeConfig,
Subsampling subsampling,
SkYUVColorSpace yuvColorSpace,
SkEncodedOrigin origin,
Siting sitingX,
Siting sitingY)
: fDimensions(dimensions)
, fPlaneConfig(planeConfig)
, fSubsampling(subsampling)
, fYUVColorSpace(yuvColorSpace)
, fOrigin(origin)
, fSitingX(sitingX)
, fSitingY(sitingY) {
if (fDimensions.isEmpty() ||
!is_plane_config_compatible_with_subsampling(planeConfig, subsampling)) {
*this = {};
SkASSERT(!this->isValid());
return;
}
SkASSERT(this->isValid());
}
size_t SkYUVAInfo::computeTotalBytes(const size_t rowBytes[kMaxPlanes],
size_t planeSizes[kMaxPlanes]) const {
if (!this->isValid()) {
return 0;
}
SkSafeMath safe;
size_t totalBytes = 0;
SkISize planeDimensions[kMaxPlanes];
int n = this->planeDimensions(planeDimensions);
for (int i = 0; i < n; ++i) {
SkASSERT(!planeDimensions[i].isEmpty());
SkASSERT(rowBytes[i]);
size_t size = safe.mul(rowBytes[i], planeDimensions[i].height());
if (planeSizes) {
planeSizes[i] = size;
}
totalBytes = safe.add(totalBytes, size);
}
if (planeSizes) {
if (safe.ok()) {
for (int i = n; i < kMaxPlanes; ++i) {
planeSizes[i] = 0;
}
} else {
for (int i = 0; n < kMaxPlanes; ++i) {
planeSizes[i] = SIZE_MAX;
}
}
}
return safe.ok() ? totalBytes : SIZE_MAX;
}
SkYUVAInfo::YUVALocations SkYUVAInfo::toYUVALocations(const uint32_t* channelFlags) const {
return GetYUVALocations(fPlaneConfig, channelFlags);
}
SkYUVAInfo SkYUVAInfo::makeSubsampling(SkYUVAInfo::Subsampling subsampling) const {
return {fDimensions, fPlaneConfig, subsampling, fYUVColorSpace, fOrigin, fSitingX, fSitingY};
}
SkYUVAInfo SkYUVAInfo::makeDimensions(SkISize dimensions) const {
return {dimensions, fPlaneConfig, fSubsampling, fYUVColorSpace, fOrigin, fSitingX, fSitingY};
}
bool SkYUVAInfo::operator==(const SkYUVAInfo& that) const {
return fPlaneConfig == that.fPlaneConfig &&
fSubsampling == that.fSubsampling &&
fYUVColorSpace == that.fYUVColorSpace &&
fDimensions == that.fDimensions &&
fSitingX == that.fSitingX &&
fSitingY == that.fSitingY &&
fOrigin == that.fOrigin;
}