Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "YCbCrUtils.h"
#include "gfx2DGlue.h"
#include "libyuv.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/gfx/Swizzle.h"
#include "ycbcr_to_rgb565.h"
#include "yuv_convert.h"
namespace mozilla {
namespace gfx {
static YUVType GetYUVType(const layers::PlanarYCbCrData& aData) {
switch (aData.mChromaSubsampling) {
case ChromaSubsampling::FULL:
return aData.mCbCrStride > 0 ? YV24 : Y8;
case ChromaSubsampling::HALF_WIDTH:
return YV16;
case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT:
return YV12;
}
MOZ_CRASH("Unknown chroma subsampling");
}
void
GetYCbCrToRGBDestFormatAndSize(const layers::PlanarYCbCrData& aData,
SurfaceFormat& aSuggestedFormat,
IntSize& aSuggestedSize)
{
YUVType yuvtype = GetYUVType(aData);
// 'prescale' is true if the scaling is to be done as part of the
// YCbCr to RGB conversion rather than on the RGB data when rendered.
bool prescale = aSuggestedSize.width > 0 && aSuggestedSize.height > 0 &&
aSuggestedSize != aData.mPictureRect.Size();
if (aSuggestedFormat == SurfaceFormat::R5G6B5_UINT16) {
#if defined(HAVE_YCBCR_TO_RGB565)
if (prescale &&
!IsScaleYCbCrToRGB565Fast(aData.mPictureRect.x,
aData.mPictureRect.y,
aData.mPictureRect.width,
aData.mPictureRect.height,
aSuggestedSize.width,
aSuggestedSize.height,
yuvtype,
FILTER_BILINEAR) &&
IsConvertYCbCrToRGB565Fast(aData.mPictureRect.x,
aData.mPictureRect.y,
aData.mPictureRect.width,
aData.mPictureRect.height,
yuvtype)) {
prescale = false;
}
#else
// yuv2rgb16 function not available
aSuggestedFormat = SurfaceFormat::B8G8R8X8;
#endif
}
else if (aSuggestedFormat != SurfaceFormat::B8G8R8X8) {
// No other formats are currently supported.
aSuggestedFormat = SurfaceFormat::B8G8R8X8;
}
if (aSuggestedFormat == SurfaceFormat::B8G8R8X8) {
/* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data.
See bugs 639415 and 640073. */
if (aData.mPictureRect.TopLeft() != IntPoint(0, 0) || yuvtype == YV24)
prescale = false;
}
if (!prescale) {
aSuggestedSize = aData.mPictureRect.Size();
}
}
static inline void
ConvertYCbCr16to8Line(uint8_t* aDst,
int aStride,
const uint16_t* aSrc,
int aStride16,
int aWidth,
int aHeight,
int aBitDepth)
{
// These values from from the comment on from libyuv's Convert16To8Row_C:
int scale;
switch (aBitDepth) {
case 10:
scale = 16384;
break;
case 12:
scale = 4096;
break;
case 16:
scale = 256;
break;
default:
MOZ_ASSERT_UNREACHABLE("invalid bit depth value");
return;
}
libyuv::Convert16To8Plane(aSrc, aStride16, aDst, aStride, scale, aWidth, aHeight);
}
struct YUV8BitData {
nsresult Init(const layers::PlanarYCbCrData& aData) {
if (aData.mColorDepth == ColorDepth::COLOR_8) {
mData = aData;
return NS_OK;
}
mData.mPictureRect = aData.mPictureRect;
// We align the destination stride to 32 bytes, so that libyuv can use
// SSE optimised code.
auto ySize = aData.YDataSize();
auto cbcrSize = aData.CbCrDataSize();
mData.mYStride = (ySize.width + 31) & ~31;
mData.mCbCrStride = (cbcrSize.width + 31) & ~31;
mData.mYUVColorSpace = aData.mYUVColorSpace;
mData.mColorDepth = ColorDepth::COLOR_8;
mData.mColorRange = aData.mColorRange;
mData.mChromaSubsampling = aData.mChromaSubsampling;
size_t yMemorySize = GetAlignedStride<1>(mData.mYStride, ySize.height);
size_t cbcrMemorySize =
GetAlignedStride<1>(mData.mCbCrStride, cbcrSize.height);
if (yMemorySize == 0) {
MOZ_DIAGNOSTIC_ASSERT(cbcrMemorySize == 0,
"CbCr without Y makes no sense");
return NS_ERROR_INVALID_ARG;
}
mYChannel = MakeUnique<uint8_t[]>(yMemorySize);
if (!mYChannel) {
return NS_ERROR_OUT_OF_MEMORY;
}
mData.mYChannel = mYChannel.get();
int bitDepth = BitDepthForColorDepth(aData.mColorDepth);
ConvertYCbCr16to8Line(mData.mYChannel, mData.mYStride,
reinterpret_cast<uint16_t*>(aData.mYChannel),
aData.mYStride / 2, ySize.width, ySize.height,
bitDepth);
if (cbcrMemorySize) {
mCbChannel = MakeUnique<uint8_t[]>(cbcrMemorySize);
if (!mCbChannel) {
return NS_ERROR_OUT_OF_MEMORY;
}
mCrChannel = MakeUnique<uint8_t[]>(cbcrMemorySize);
if (!mCrChannel) {
return NS_ERROR_OUT_OF_MEMORY;
}
mData.mCbChannel = mCbChannel.get();
mData.mCrChannel = mCrChannel.get();
ConvertYCbCr16to8Line(mData.mCbChannel, mData.mCbCrStride,
reinterpret_cast<uint16_t*>(aData.mCbChannel),
aData.mCbCrStride / 2, cbcrSize.width,
cbcrSize.height, bitDepth);
ConvertYCbCr16to8Line(mData.mCrChannel, mData.mCbCrStride,
reinterpret_cast<uint16_t*>(aData.mCrChannel),
aData.mCbCrStride / 2, cbcrSize.width,
cbcrSize.height, bitDepth);
}
if (aData.mAlpha) {
int32_t alphaStride8bpp = (aData.mAlpha->mSize.width + 31) & ~31;
size_t alphaSize =
GetAlignedStride<1>(alphaStride8bpp, aData.mAlpha->mSize.height);
mAlphaChannel = MakeUnique<uint8_t[]>(alphaSize);
if (!mAlphaChannel) {
return NS_ERROR_OUT_OF_MEMORY;
}
mData.mAlpha.emplace();
mData.mAlpha->mPremultiplied = aData.mAlpha->mPremultiplied;
mData.mAlpha->mSize = aData.mAlpha->mSize;
mData.mAlpha->mChannel = mAlphaChannel.get();
ConvertYCbCr16to8Line(mData.mAlpha->mChannel, alphaStride8bpp,
reinterpret_cast<uint16_t*>(aData.mAlpha->mChannel),
aData.mYStride / 2, aData.mAlpha->mSize.width,
aData.mAlpha->mSize.height,
BitDepthForColorDepth(aData.mColorDepth));
}
return NS_OK;
}
const layers::PlanarYCbCrData& Get8BitData() { return mData; }
layers::PlanarYCbCrData mData;
UniquePtr<uint8_t[]> mYChannel;
UniquePtr<uint8_t[]> mCbChannel;
UniquePtr<uint8_t[]> mCrChannel;
UniquePtr<uint8_t[]> mAlphaChannel;
};
static nsresult ScaleYCbCrToRGB(const layers::PlanarYCbCrData& aData,
const SurfaceFormat& aDestFormat,
const IntSize& aDestSize,
unsigned char* aDestBuffer,
int32_t aStride,
YUVType aYUVType) {
#if defined(HAVE_YCBCR_TO_RGB565)
if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) {
ScaleYCbCrToRGB565(aData.mYChannel,
aData.mCbChannel,
aData.mCrChannel,
aDestBuffer,
aData.mPictureRect.x,
aData.mPictureRect.y,
aData.mPictureRect.width,
aData.mPictureRect.height,
aDestSize.width,
aDestSize.height,
aData.mYStride,
aData.mCbCrStride,
aStride,
aYUVType,
FILTER_BILINEAR);
return NS_OK;
}
#endif
return ScaleYCbCrToRGB32(aData.mYChannel,
aData.mCbChannel,
aData.mCrChannel,
aDestBuffer,
aData.mPictureRect.width,
aData.mPictureRect.height,
aDestSize.width,
aDestSize.height,
aData.mYStride,
aData.mCbCrStride,
aStride,
aYUVType,
aData.mYUVColorSpace,
FILTER_BILINEAR);
}
static nsresult ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData,
const SurfaceFormat& aDestFormat,
unsigned char* aDestBuffer,
int32_t aStride,
YUVType aYUVType,
RGB32Type aRGB32Type) {
#if defined(HAVE_YCBCR_TO_RGB565)
if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) {
ConvertYCbCrToRGB565(aData.mYChannel,
aData.mCbChannel,
aData.mCrChannel,
aDestBuffer,
aData.mPictureRect.x,
aData.mPictureRect.y,
aData.mPictureRect.width,
aData.mPictureRect.height,
aData.mYStride,
aData.mCbCrStride,
aStride,
aYUVType);
return NS_OK;
}
#endif
return ConvertYCbCrToRGB32(aData.mYChannel,
aData.mCbChannel,
aData.mCrChannel,
aDestBuffer,
aData.mPictureRect.x,
aData.mPictureRect.y,
aData.mPictureRect.width,
aData.mPictureRect.height,
aData.mYStride,
aData.mCbCrStride,
aStride,
aYUVType,
aData.mYUVColorSpace,
aData.mColorRange,
aRGB32Type);
}
nsresult ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData,
const SurfaceFormat& aDestFormat,
const IntSize& aDestSize, unsigned char* aDestBuffer,
int32_t aStride) {
// ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the
// luma plane is odd sized. Monochrome images have 0-sized CbCr planes
YUVType yuvtype = GetYUVType(aData);
YUV8BitData data;
nsresult result = data.Init(aData);
if (NS_FAILED(result)) {
return result;
}
const layers::PlanarYCbCrData& srcData = data.Get8BitData();
// Convert from YCbCr to RGB now, scaling the image if needed.
if (aDestSize != srcData.mPictureRect.Size()) {
result = ScaleYCbCrToRGB(srcData, aDestFormat, aDestSize, aDestBuffer,
aStride, yuvtype);
} else { // no prescale
result = ConvertYCbCrToRGB(srcData, aDestFormat, aDestBuffer, aStride,
yuvtype, RGB32Type::ARGB);
}
if (NS_FAILED(result)) {
return result;
}
#if MOZ_BIG_ENDIAN()
// libyuv makes endian-correct result, which needs to be swapped to BGRX
if (aDestFormat != SurfaceFormat::R5G6B5_UINT16) {
if (!gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::X8R8G8B8,
aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8X8,
aDestSize)) {
return NS_ERROR_UNEXPECTED;
}
}
#endif
return NS_OK;
}
void FillAlphaToRGBA(const uint8_t* aAlpha, const int32_t aAlphaStride,
uint8_t* aBuffer, const int32_t aWidth,
const int32_t aHeight, const gfx::SurfaceFormat& aFormat) {
MOZ_ASSERT(aAlphaStride >= aWidth);
// required for SurfaceFormatBit::OS_A
MOZ_ASSERT(aFormat == SurfaceFormat::B8G8R8A8 ||
aFormat == SurfaceFormat::R8G8B8A8);
const int bpp = BytesPerPixel(aFormat);
const size_t rgbaStride = aWidth * bpp;
const uint8_t* src = aAlpha;
for (int32_t h = 0; h < aHeight; ++h) {
size_t offset = static_cast<size_t>(SurfaceFormatBit::OS_A) / 8;
for (int32_t w = 0; w < aWidth; ++w) {
aBuffer[offset] = src[w];
offset += bpp;
}
src += aAlphaStride;
aBuffer += rgbaStride;
}
}
nsresult ConvertYCbCrToRGB32(const layers::PlanarYCbCrData& aData,
const SurfaceFormat& aDestFormat,
unsigned char* aDestBuffer, int32_t aStride,
PremultFunc premultiplyAlphaOp) {
MOZ_ASSERT(aDestFormat == SurfaceFormat::B8G8R8A8 ||
aDestFormat == SurfaceFormat::B8G8R8X8 ||
aDestFormat == SurfaceFormat::R8G8B8A8 ||
aDestFormat == SurfaceFormat::R8G8B8X8);
YUVType yuvtype = GetYUVType(aData);
YUV8BitData data8pp;
nsresult result = data8pp.Init(aData);
if (NS_FAILED(result)) {
return result;
}
const layers::PlanarYCbCrData& data = data8pp.Get8BitData();
// The order of SurfaceFormat's R, G, B, A is reversed compared to libyuv's
// order.
RGB32Type rgb32Type = aDestFormat == SurfaceFormat::B8G8R8A8 ||
aDestFormat == SurfaceFormat::B8G8R8X8
? RGB32Type::ARGB
: RGB32Type::ABGR;
result = ConvertYCbCrToRGB(data, aDestFormat, aDestBuffer, aStride, yuvtype,
rgb32Type);
if (NS_FAILED(result)) {
return result;
}
bool needAlpha = aDestFormat == SurfaceFormat::B8G8R8A8 ||
aDestFormat == SurfaceFormat::R8G8B8A8;
if (data.mAlpha && needAlpha) {
// Alpha stride should be same as the Y stride.
FillAlphaToRGBA(data.mAlpha->mChannel, data.mYStride, aDestBuffer,
data.mPictureRect.width, aData.mPictureRect.height,
aDestFormat);
if (premultiplyAlphaOp) {
result = ToNSResult(premultiplyAlphaOp(aDestBuffer, aStride, aDestBuffer,
aStride, aData.mPictureRect.width,
aData.mPictureRect.height));
if (NS_FAILED(result)) {
return result;
}
}
}
#if MOZ_BIG_ENDIAN()
// libyuv makes endian-correct result, which needs to be reversed to BGR* or
// RGB*.
if (!gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::X8R8G8B8,
aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8X8,
aData.mPictureRect.Size())) {
return NS_ERROR_UNEXPECTED;
}
#endif
return NS_OK;
}
nsresult ConvertI420AlphaToARGB(const uint8_t* aSrcY, const uint8_t* aSrcU,
const uint8_t* aSrcV, const uint8_t* aSrcA,
int aSrcStrideYA, int aSrcStrideUV,
uint8_t* aDstARGB, int aDstStrideARGB,
int aWidth, int aHeight) {
nsresult result = ConvertI420AlphaToARGB32(
aSrcY, aSrcU, aSrcV, aSrcA, aDstARGB, aWidth, aHeight, aSrcStrideYA,
aSrcStrideUV, aDstStrideARGB);
if (NS_FAILED(result)) {
return result;
}
#if MOZ_BIG_ENDIAN()
// libyuv makes endian-correct result, which needs to be swapped to BGRA
if (!gfx::SwizzleData(aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::A8R8G8B8,
aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::B8G8R8A8,
IntSize(aWidth, aHeight))) {
return NS_ERROR_UNEXPECTED;
}
#endif
return NS_OK;
}
} // namespace gfx
} // namespace mozilla