| /* | |
| * 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 "include/codec/SkEncodedOrigin.h" | |
| #include "include/core/SkColor.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; | |
| } | |
| #if defined(SK_ENABLE_YUVA_PACKED_422) | |
| if (config == SkYUVAInfo::PlaneConfig::kYUYV) { | |
| return subsampling == SkYUVAInfo::Subsampling::k422; | |
| } | |
| #endif | |
| 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: | |
| #if defined(SK_ENABLE_YUVA_PACKED_422) | |
| case PlaneConfig::kYUYV: | |
| #endif | |
| 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; | |
| #if defined(SK_ENABLE_YUVA_PACKED_422) | |
| case PlaneConfig::kYUYV: | |
| planeDimensions[0] = {w, h}; | |
| SkASSERT(w % 2 == 0); | |
| return 1; | |
| #endif | |
| } | |
| 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; | |
| } | |
| #if defined(SK_ENABLE_YUVA_PACKED_422) | |
| case PlaneConfig::kYUYV: { | |
| static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {0, 1}, {0, 1}, {-1, -1}}; | |
| planesAndIndices = kPlanesAndIndices; | |
| break; | |
| } | |
| #endif | |
| } | |
| 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; | |
| #if defined(SK_ENABLE_YUVA_PACKED_422) | |
| case PlaneConfig::kYUYV: return false; | |
| #endif | |
| } | |
| 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; | |
| } | |
| #if defined(SK_ENABLE_YUVA_PACKED_422) | |
| if (planeConfig == PlaneConfig::kYUYV) { | |
| int memoryWidth = SkEncodedOriginSwapsWidthHeight(origin) ? fDimensions.height() | |
| : fDimensions.width(); | |
| if (memoryWidth % 2 != 0) { | |
| *this = {}; | |
| SkASSERT(!this->isValid()); | |
| return; | |
| } | |
| } | |
| #endif | |
| 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; | |
| } |