/* * 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 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 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 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(channelIdx); return true; } return false; case kRGB_SkColorChannelFlags: if (channelIdx >= 0 && channelIdx <= 2) { *channel = static_cast(channelIdx); return true; } return false; case kRGBA_SkColorChannelFlags: if (channelIdx >= 0 && channelIdx <= 3) { *channel = static_cast(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; }