tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

YCbCrUtils.cpp (16097B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
      2 * This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "YCbCrUtils.h"
      7 
      8 #include "gfx2DGlue.h"
      9 #include "libyuv.h"
     10 #include "mozilla/EndianUtils.h"
     11 #include "mozilla/gfx/Swizzle.h"
     12 #include "ycbcr_to_rgb565.h"
     13 #include "yuv_convert.h"
     14 
     15 namespace mozilla {
     16 namespace gfx {
     17 
     18 static YUVType GetYUVType(const layers::PlanarYCbCrData& aData) {
     19  switch (aData.mChromaSubsampling) {
     20    case ChromaSubsampling::FULL:
     21      return aData.mCbCrStride > 0 ? YV24 : Y8;
     22    case ChromaSubsampling::HALF_WIDTH:
     23      return YV16;
     24    case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT:
     25      return YV12;
     26  }
     27  MOZ_CRASH("Unknown chroma subsampling");
     28 }
     29 
     30 void
     31 GetYCbCrToRGBDestFormatAndSize(const layers::PlanarYCbCrData& aData,
     32                               SurfaceFormat& aSuggestedFormat,
     33                               IntSize& aSuggestedSize)
     34 {
     35  YUVType yuvtype = GetYUVType(aData);
     36 
     37  // 'prescale' is true if the scaling is to be done as part of the
     38  // YCbCr to RGB conversion rather than on the RGB data when rendered.
     39  bool prescale = aSuggestedSize.width > 0 && aSuggestedSize.height > 0 &&
     40                  aSuggestedSize != aData.mPictureRect.Size();
     41 
     42  if (aSuggestedFormat == SurfaceFormat::R5G6B5_UINT16) {
     43 #if defined(HAVE_YCBCR_TO_RGB565)
     44    if (prescale &&
     45        !IsScaleYCbCrToRGB565Fast(aData.mPictureRect.x,
     46                                  aData.mPictureRect.y,
     47                                  aData.mPictureRect.width,
     48                                  aData.mPictureRect.height,
     49                                  aSuggestedSize.width,
     50                                  aSuggestedSize.height,
     51                                  yuvtype,
     52                                  FILTER_BILINEAR) &&
     53        IsConvertYCbCrToRGB565Fast(aData.mPictureRect.x,
     54                                   aData.mPictureRect.y,
     55                                   aData.mPictureRect.width,
     56                                   aData.mPictureRect.height,
     57                                   yuvtype)) {
     58      prescale = false;
     59    }
     60 #else
     61    // yuv2rgb16 function not available
     62    aSuggestedFormat = SurfaceFormat::B8G8R8X8;
     63 #endif
     64  }
     65  else if (aSuggestedFormat != SurfaceFormat::B8G8R8X8) {
     66    // No other formats are currently supported.
     67    aSuggestedFormat = SurfaceFormat::B8G8R8X8;
     68  }
     69  if (aSuggestedFormat == SurfaceFormat::B8G8R8X8) {
     70    /* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data.
     71     See bugs 639415 and 640073. */
     72    if (aData.mPictureRect.TopLeft() != IntPoint(0, 0) || yuvtype == YV24)
     73      prescale = false;
     74  }
     75  if (!prescale) {
     76    aSuggestedSize = aData.mPictureRect.Size();
     77  }
     78 }
     79 
     80 static inline void
     81 ConvertYCbCr16to8Line(uint8_t* aDst,
     82                      int aStride,
     83                      const uint16_t* aSrc,
     84                      int aStride16,
     85                      int aWidth,
     86                      int aHeight,
     87                      int aBitDepth)
     88 {
     89  // These values from from the comment on from libyuv's Convert16To8Row_C:
     90  int scale;
     91  switch (aBitDepth) {
     92    case 10:
     93      scale = 16384;
     94      break;
     95    case 12:
     96      scale = 4096;
     97      break;
     98    case 16:
     99      scale = 256;
    100      break;
    101    default:
    102      MOZ_ASSERT_UNREACHABLE("invalid bit depth value");
    103      return;
    104  }
    105 
    106  libyuv::Convert16To8Plane(aSrc, aStride16, aDst, aStride, scale, aWidth, aHeight);
    107 }
    108 
    109 struct YUV8BitData {
    110  nsresult Init(const layers::PlanarYCbCrData& aData) {
    111    if (aData.mColorDepth == ColorDepth::COLOR_8) {
    112      mData = aData;
    113      return NS_OK;
    114    }
    115 
    116    mData.mPictureRect = aData.mPictureRect;
    117 
    118    // We align the destination stride to 32 bytes, so that libyuv can use
    119    // SSE optimised code.
    120    auto ySize = aData.YDataSize();
    121    auto cbcrSize = aData.CbCrDataSize();
    122    mData.mYStride = (ySize.width + 31) & ~31;
    123    mData.mCbCrStride = (cbcrSize.width + 31) & ~31;
    124    mData.mYUVColorSpace = aData.mYUVColorSpace;
    125    mData.mColorDepth = ColorDepth::COLOR_8;
    126    mData.mColorRange = aData.mColorRange;
    127    mData.mChromaSubsampling = aData.mChromaSubsampling;
    128 
    129    size_t yMemorySize = GetAlignedStride<1>(mData.mYStride, ySize.height);
    130    size_t cbcrMemorySize =
    131        GetAlignedStride<1>(mData.mCbCrStride, cbcrSize.height);
    132    if (yMemorySize == 0) {
    133      MOZ_DIAGNOSTIC_ASSERT(cbcrMemorySize == 0,
    134                            "CbCr without Y makes no sense");
    135      return NS_ERROR_INVALID_ARG;
    136    }
    137    mYChannel = MakeUnique<uint8_t[]>(yMemorySize);
    138    if (!mYChannel) {
    139      return NS_ERROR_OUT_OF_MEMORY;
    140    }
    141 
    142    mData.mYChannel = mYChannel.get();
    143 
    144    int bitDepth = BitDepthForColorDepth(aData.mColorDepth);
    145 
    146    ConvertYCbCr16to8Line(mData.mYChannel, mData.mYStride,
    147                          reinterpret_cast<uint16_t*>(aData.mYChannel),
    148                          aData.mYStride / 2, ySize.width, ySize.height,
    149                          bitDepth);
    150 
    151    if (cbcrMemorySize) {
    152      mCbChannel = MakeUnique<uint8_t[]>(cbcrMemorySize);
    153      if (!mCbChannel) {
    154        return NS_ERROR_OUT_OF_MEMORY;
    155      }
    156      mCrChannel = MakeUnique<uint8_t[]>(cbcrMemorySize);
    157      if (!mCrChannel) {
    158        return NS_ERROR_OUT_OF_MEMORY;
    159      }
    160 
    161      mData.mCbChannel = mCbChannel.get();
    162      mData.mCrChannel = mCrChannel.get();
    163 
    164      ConvertYCbCr16to8Line(mData.mCbChannel, mData.mCbCrStride,
    165                            reinterpret_cast<uint16_t*>(aData.mCbChannel),
    166                            aData.mCbCrStride / 2, cbcrSize.width,
    167                            cbcrSize.height, bitDepth);
    168 
    169      ConvertYCbCr16to8Line(mData.mCrChannel, mData.mCbCrStride,
    170                            reinterpret_cast<uint16_t*>(aData.mCrChannel),
    171                            aData.mCbCrStride / 2, cbcrSize.width,
    172                            cbcrSize.height, bitDepth);
    173    }
    174    if (aData.mAlpha) {
    175      int32_t alphaStride8bpp = (aData.mAlpha->mSize.width + 31) & ~31;
    176      size_t alphaSize =
    177          GetAlignedStride<1>(alphaStride8bpp, aData.mAlpha->mSize.height);
    178      mAlphaChannel = MakeUnique<uint8_t[]>(alphaSize);
    179      if (!mAlphaChannel) {
    180        return NS_ERROR_OUT_OF_MEMORY;
    181      }
    182 
    183      mData.mAlpha.emplace();
    184      mData.mAlpha->mPremultiplied = aData.mAlpha->mPremultiplied;
    185      mData.mAlpha->mSize = aData.mAlpha->mSize;
    186      mData.mAlpha->mChannel = mAlphaChannel.get();
    187 
    188      ConvertYCbCr16to8Line(mData.mAlpha->mChannel, alphaStride8bpp,
    189                            reinterpret_cast<uint16_t*>(aData.mAlpha->mChannel),
    190                            aData.mYStride / 2, aData.mAlpha->mSize.width,
    191                            aData.mAlpha->mSize.height,
    192                            BitDepthForColorDepth(aData.mColorDepth));
    193    }
    194    return NS_OK;
    195  }
    196 
    197  const layers::PlanarYCbCrData& Get8BitData() { return mData; }
    198 
    199  layers::PlanarYCbCrData mData;
    200  UniquePtr<uint8_t[]> mYChannel;
    201  UniquePtr<uint8_t[]> mCbChannel;
    202  UniquePtr<uint8_t[]> mCrChannel;
    203  UniquePtr<uint8_t[]> mAlphaChannel;
    204 };
    205 
    206 static nsresult ScaleYCbCrToRGB(const layers::PlanarYCbCrData& aData,
    207                                const SurfaceFormat& aDestFormat,
    208                                const IntSize& aDestSize,
    209                                unsigned char* aDestBuffer,
    210                                int32_t aStride,
    211                                YUVType aYUVType) {
    212 #if defined(HAVE_YCBCR_TO_RGB565)
    213  if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) {
    214    ScaleYCbCrToRGB565(aData.mYChannel,
    215                       aData.mCbChannel,
    216                       aData.mCrChannel,
    217                       aDestBuffer,
    218                       aData.mPictureRect.x,
    219                       aData.mPictureRect.y,
    220                       aData.mPictureRect.width,
    221                       aData.mPictureRect.height,
    222                       aDestSize.width,
    223                       aDestSize.height,
    224                       aData.mYStride,
    225                       aData.mCbCrStride,
    226                       aStride,
    227                       aYUVType,
    228                       FILTER_BILINEAR);
    229    return NS_OK;
    230  }
    231 #endif
    232  return ScaleYCbCrToRGB32(aData.mYChannel,
    233                           aData.mCbChannel,
    234                           aData.mCrChannel,
    235                           aDestBuffer,
    236                           aData.mPictureRect.width,
    237                           aData.mPictureRect.height,
    238                           aDestSize.width,
    239                           aDestSize.height,
    240                           aData.mYStride,
    241                           aData.mCbCrStride,
    242                           aStride,
    243                           aYUVType,
    244                           aData.mYUVColorSpace,
    245                           FILTER_BILINEAR);
    246 }
    247 
    248 static nsresult ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData,
    249                                  const SurfaceFormat& aDestFormat,
    250                                  unsigned char* aDestBuffer,
    251                                  int32_t aStride,
    252                                  YUVType aYUVType,
    253                                  RGB32Type aRGB32Type) {
    254 #if defined(HAVE_YCBCR_TO_RGB565)
    255  if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) {
    256    ConvertYCbCrToRGB565(aData.mYChannel,
    257                         aData.mCbChannel,
    258                         aData.mCrChannel,
    259                         aDestBuffer,
    260                         aData.mPictureRect.x,
    261                         aData.mPictureRect.y,
    262                         aData.mPictureRect.width,
    263                         aData.mPictureRect.height,
    264                         aData.mYStride,
    265                         aData.mCbCrStride,
    266                         aStride,
    267                         aYUVType);
    268    return NS_OK;
    269  }
    270 #endif
    271  return ConvertYCbCrToRGB32(aData.mYChannel,
    272                             aData.mCbChannel,
    273                             aData.mCrChannel,
    274                             aDestBuffer,
    275                             aData.mPictureRect.x,
    276                             aData.mPictureRect.y,
    277                             aData.mPictureRect.width,
    278                             aData.mPictureRect.height,
    279                             aData.mYStride,
    280                             aData.mCbCrStride,
    281                             aStride,
    282                             aYUVType,
    283                             aData.mYUVColorSpace,
    284                             aData.mColorRange,
    285                             aRGB32Type);
    286 }
    287 
    288 nsresult ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData,
    289                           const SurfaceFormat& aDestFormat,
    290                           const IntSize& aDestSize, unsigned char* aDestBuffer,
    291                           int32_t aStride) {
    292  // ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the
    293  // luma plane is odd sized. Monochrome images have 0-sized CbCr planes
    294  YUVType yuvtype = GetYUVType(aData);
    295 
    296  YUV8BitData data;
    297  nsresult result = data.Init(aData);
    298  if (NS_FAILED(result)) {
    299    return result;
    300  }
    301  const layers::PlanarYCbCrData& srcData = data.Get8BitData();
    302 
    303  // Convert from YCbCr to RGB now, scaling the image if needed.
    304  if (aDestSize != srcData.mPictureRect.Size()) {
    305    result = ScaleYCbCrToRGB(srcData, aDestFormat, aDestSize, aDestBuffer,
    306                             aStride, yuvtype);
    307  } else {  // no prescale
    308    result = ConvertYCbCrToRGB(srcData, aDestFormat, aDestBuffer, aStride,
    309                               yuvtype, RGB32Type::ARGB);
    310  }
    311  if (NS_FAILED(result)) {
    312    return result;
    313  }
    314 
    315 #if MOZ_BIG_ENDIAN()
    316  // libyuv makes endian-correct result, which needs to be swapped to BGRX
    317  if (aDestFormat != SurfaceFormat::R5G6B5_UINT16) {
    318    if (!gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::X8R8G8B8,
    319                          aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8X8,
    320                          aDestSize)) {
    321      return NS_ERROR_UNEXPECTED;
    322    }
    323  }
    324 #endif
    325  return NS_OK;
    326 }
    327 
    328 void FillAlphaToRGBA(const uint8_t* aAlpha, const int32_t aAlphaStride,
    329                     uint8_t* aBuffer, const int32_t aWidth,
    330                     const int32_t aHeight, const gfx::SurfaceFormat& aFormat) {
    331  MOZ_ASSERT(aAlphaStride >= aWidth);
    332  // required for SurfaceFormatBit::OS_A
    333  MOZ_ASSERT(aFormat == SurfaceFormat::B8G8R8A8 ||
    334             aFormat == SurfaceFormat::R8G8B8A8);
    335 
    336  const int bpp = BytesPerPixel(aFormat);
    337  const size_t rgbaStride = aWidth * bpp;
    338  const uint8_t* src = aAlpha;
    339  for (int32_t h = 0; h < aHeight; ++h) {
    340    size_t offset = static_cast<size_t>(SurfaceFormatBit::OS_A) / 8;
    341    for (int32_t w = 0; w < aWidth; ++w) {
    342      aBuffer[offset] = src[w];
    343      offset += bpp;
    344    }
    345    src += aAlphaStride;
    346    aBuffer += rgbaStride;
    347  }
    348 }
    349 
    350 nsresult ConvertYCbCrToRGB32(const layers::PlanarYCbCrData& aData,
    351                             const SurfaceFormat& aDestFormat,
    352                             unsigned char* aDestBuffer, int32_t aStride,
    353                             PremultFunc premultiplyAlphaOp) {
    354  MOZ_ASSERT(aDestFormat == SurfaceFormat::B8G8R8A8 ||
    355             aDestFormat == SurfaceFormat::B8G8R8X8 ||
    356             aDestFormat == SurfaceFormat::R8G8B8A8 ||
    357             aDestFormat == SurfaceFormat::R8G8B8X8);
    358 
    359  YUVType yuvtype = GetYUVType(aData);
    360 
    361  YUV8BitData data8pp;
    362  nsresult result = data8pp.Init(aData);
    363  if (NS_FAILED(result)) {
    364    return result;
    365  }
    366  const layers::PlanarYCbCrData& data = data8pp.Get8BitData();
    367 
    368  // The order of SurfaceFormat's R, G, B, A is reversed compared to libyuv's
    369  // order.
    370  RGB32Type rgb32Type = aDestFormat == SurfaceFormat::B8G8R8A8 ||
    371                                aDestFormat == SurfaceFormat::B8G8R8X8
    372                            ? RGB32Type::ARGB
    373                            : RGB32Type::ABGR;
    374 
    375  result = ConvertYCbCrToRGB(data, aDestFormat, aDestBuffer, aStride, yuvtype,
    376                             rgb32Type);
    377  if (NS_FAILED(result)) {
    378    return result;
    379  }
    380 
    381  bool needAlpha = aDestFormat == SurfaceFormat::B8G8R8A8 ||
    382                   aDestFormat == SurfaceFormat::R8G8B8A8;
    383  if (data.mAlpha && needAlpha) {
    384    // Alpha stride should be same as the Y stride.
    385    FillAlphaToRGBA(data.mAlpha->mChannel, data.mYStride, aDestBuffer,
    386                    data.mPictureRect.width, aData.mPictureRect.height,
    387                    aDestFormat);
    388 
    389    if (premultiplyAlphaOp) {
    390      result = ToNSResult(premultiplyAlphaOp(aDestBuffer, aStride, aDestBuffer,
    391                                             aStride, aData.mPictureRect.width,
    392                                             aData.mPictureRect.height));
    393      if (NS_FAILED(result)) {
    394        return result;
    395      }
    396    }
    397  }
    398 
    399 #if MOZ_BIG_ENDIAN()
    400  // libyuv makes endian-correct result, which needs to be reversed to BGR* or
    401  // RGB*.
    402  if (!gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::X8R8G8B8,
    403                        aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8X8,
    404                        aData.mPictureRect.Size())) {
    405    return NS_ERROR_UNEXPECTED;
    406  }
    407 #endif
    408  return NS_OK;
    409 }
    410 
    411 nsresult ConvertI420AlphaToARGB(const uint8_t* aSrcY, const uint8_t* aSrcU,
    412                                const uint8_t* aSrcV, const uint8_t* aSrcA,
    413                                int aSrcStrideYA, int aSrcStrideUV,
    414                                uint8_t* aDstARGB, int aDstStrideARGB,
    415                                int aWidth, int aHeight) {
    416  nsresult result = ConvertI420AlphaToARGB32(
    417      aSrcY, aSrcU, aSrcV, aSrcA, aDstARGB, aWidth, aHeight, aSrcStrideYA,
    418      aSrcStrideUV, aDstStrideARGB);
    419  if (NS_FAILED(result)) {
    420    return result;
    421  }
    422 #if MOZ_BIG_ENDIAN()
    423  // libyuv makes endian-correct result, which needs to be swapped to BGRA
    424  if (!gfx::SwizzleData(aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::A8R8G8B8,
    425                        aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::B8G8R8A8,
    426                        IntSize(aWidth, aHeight))) {
    427    return NS_ERROR_UNEXPECTED;
    428  }
    429 #endif
    430  return NS_OK;
    431 }
    432 
    433 }  // namespace gfx
    434 }  // namespace mozilla