tor-browser

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

ImageConversion.cpp (29639B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
      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 file,
      4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "ImageConversion.h"
      7 
      8 #include "ImageContainer.h"
      9 #include "YCbCrUtils.h"
     10 #include "libyuv/convert.h"
     11 #include "libyuv/convert_from_argb.h"
     12 #include "libyuv/scale_argb.h"
     13 #include "mozilla/PodOperations.h"
     14 #include "mozilla/RefPtr.h"
     15 #include "mozilla/dom/ImageBitmapBinding.h"
     16 #include "mozilla/dom/ImageUtils.h"
     17 #include "mozilla/gfx/Point.h"
     18 #include "mozilla/gfx/Swizzle.h"
     19 #include "nsThreadUtils.h"
     20 #include "skia/include/core/SkBitmap.h"
     21 #include "skia/include/core/SkColorSpace.h"
     22 #include "skia/include/core/SkImage.h"
     23 #include "skia/include/core/SkImageInfo.h"
     24 
     25 using mozilla::ImageFormat;
     26 using mozilla::dom::ImageBitmapFormat;
     27 using mozilla::dom::ImageUtils;
     28 using mozilla::gfx::DataSourceSurface;
     29 using mozilla::gfx::IntSize;
     30 using mozilla::gfx::SourceSurface;
     31 using mozilla::gfx::SurfaceFormat;
     32 using mozilla::layers::Image;
     33 using mozilla::layers::PlanarYCbCrData;
     34 using mozilla::layers::PlanarYCbCrImage;
     35 
     36 static const PlanarYCbCrData* GetPlanarYCbCrData(Image* aImage) {
     37  switch (aImage->GetFormat()) {
     38    case ImageFormat::PLANAR_YCBCR:
     39      return aImage->AsPlanarYCbCrImage()->GetData();
     40    case ImageFormat::NV_IMAGE:
     41      return aImage->AsNVImage()->GetData();
     42    default:
     43      return nullptr;
     44  }
     45 }
     46 
     47 static nsresult MapRv(int aRv) {
     48  // Docs for libyuv::ConvertToI420 say:
     49  // Returns 0 for successful; -1 for invalid parameter. Non-zero for failure.
     50  switch (aRv) {
     51    case 0:
     52      return NS_OK;
     53    case -1:
     54      return NS_ERROR_INVALID_ARG;
     55    default:
     56      return NS_ERROR_FAILURE;
     57  }
     58 }
     59 
     60 namespace mozilla {
     61 
     62 already_AddRefed<SourceSurface> GetSourceSurface(Image* aImage) {
     63  if (!aImage->AsGLImage() || NS_IsMainThread()) {
     64    return aImage->GetAsSourceSurface();
     65  }
     66 
     67  // GLImage::GetAsSourceSurface() only supports main thread
     68  RefPtr<SourceSurface> surf;
     69  NS_DispatchAndSpinEventLoopUntilComplete(
     70      "ImageToI420::GLImage::GetSourceSurface"_ns,
     71      mozilla::GetMainThreadSerialEventTarget(),
     72      NS_NewRunnableFunction(
     73          "ImageToI420::GLImage::GetSourceSurface",
     74          [&aImage, &surf]() { surf = aImage->GetAsSourceSurface(); }));
     75 
     76  return surf.forget();
     77 }
     78 
     79 nsresult ConvertToI420(Image* aImage, uint8_t* aDestY, int aDestStrideY,
     80                       uint8_t* aDestU, int aDestStrideU, uint8_t* aDestV,
     81                       int aDestStrideV, const IntSize& aDestSize) {
     82  if (!aImage->IsValid()) {
     83    return NS_ERROR_INVALID_ARG;
     84  }
     85 
     86  const IntSize imageSize = aImage->GetSize();
     87  auto srcPixelCount = CheckedInt<int32_t>(imageSize.width) * imageSize.height;
     88  auto dstPixelCount = CheckedInt<int32_t>(aDestSize.width) * aDestSize.height;
     89  if (!srcPixelCount.isValid() || !dstPixelCount.isValid()) {
     90    MOZ_ASSERT_UNREACHABLE("Bad input or output sizes");
     91    return NS_ERROR_INVALID_ARG;
     92  }
     93 
     94  // If we are downscaling, we prefer an early scale. If we are upscaling, we
     95  // prefer a late scale. This minimizes the number of pixel manipulations.
     96  // Depending on the input format, we may be forced to do a late scale after
     97  // conversion to I420, because we don't support scaling the input format.
     98  const bool needsScale = imageSize != aDestSize;
     99  bool earlyScale = srcPixelCount.value() > dstPixelCount.value();
    100 
    101  Maybe<DataSourceSurface::ScopedMap> surfaceMap;
    102  SurfaceFormat surfaceFormat = SurfaceFormat::UNKNOWN;
    103 
    104  const PlanarYCbCrData* data = GetPlanarYCbCrData(aImage);
    105  Maybe<dom::ImageBitmapFormat> format;
    106  if (data) {
    107    const ImageUtils imageUtils(aImage);
    108    format = imageUtils.GetFormat();
    109    if (format.isNothing()) {
    110      MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented");
    111      return NS_ERROR_NOT_IMPLEMENTED;
    112    }
    113 
    114    switch (format.value()) {
    115      case ImageBitmapFormat::YUV420P:
    116        // Since the input and output formats match, we can copy or scale
    117        // directly to the output buffer.
    118        if (needsScale) {
    119          return MapRv(libyuv::I420Scale(
    120              data->mYChannel, data->mYStride, data->mCbChannel,
    121              data->mCbCrStride, data->mCrChannel, data->mCbCrStride,
    122              imageSize.width, imageSize.height, aDestY, aDestStrideY, aDestU,
    123              aDestStrideU, aDestV, aDestStrideV, aDestSize.width,
    124              aDestSize.height, libyuv::FilterMode::kFilterBox));
    125        }
    126        return MapRv(libyuv::I420ToI420(
    127            data->mYChannel, data->mYStride, data->mCbChannel,
    128            data->mCbCrStride, data->mCrChannel, data->mCbCrStride, aDestY,
    129            aDestStrideY, aDestU, aDestStrideU, aDestV, aDestStrideV,
    130            aDestSize.width, aDestSize.height));
    131      case ImageBitmapFormat::YUV422P:
    132        if (!needsScale) {
    133          return MapRv(libyuv::I422ToI420(
    134              data->mYChannel, data->mYStride, data->mCbChannel,
    135              data->mCbCrStride, data->mCrChannel, data->mCbCrStride, aDestY,
    136              aDestStrideY, aDestU, aDestStrideU, aDestV, aDestStrideV,
    137              aDestSize.width, aDestSize.height));
    138        }
    139        break;
    140      case ImageBitmapFormat::YUV444P:
    141        if (!needsScale) {
    142          return MapRv(libyuv::I444ToI420(
    143              data->mYChannel, data->mYStride, data->mCbChannel,
    144              data->mCbCrStride, data->mCrChannel, data->mCbCrStride, aDestY,
    145              aDestStrideY, aDestU, aDestStrideU, aDestV, aDestStrideV,
    146              aDestSize.width, aDestSize.height));
    147        }
    148        break;
    149      case ImageBitmapFormat::YUV420SP_NV12:
    150        if (!needsScale) {
    151          return MapRv(libyuv::NV12ToI420(
    152              data->mYChannel, data->mYStride, data->mCbChannel,
    153              data->mCbCrStride, aDestY, aDestStrideY, aDestU, aDestStrideU,
    154              aDestV, aDestStrideV, aDestSize.width, aDestSize.height));
    155        }
    156        break;
    157      case ImageBitmapFormat::YUV420SP_NV21:
    158        if (!needsScale) {
    159          return MapRv(libyuv::NV21ToI420(
    160              data->mYChannel, data->mYStride, data->mCrChannel,
    161              data->mCbCrStride, aDestY, aDestStrideY, aDestU, aDestStrideU,
    162              aDestV, aDestStrideV, aDestSize.width, aDestSize.height));
    163        }
    164        earlyScale = false;
    165        break;
    166      default:
    167        MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented");
    168        return NS_ERROR_NOT_IMPLEMENTED;
    169    }
    170  } else {
    171    RefPtr<SourceSurface> surface = GetSourceSurface(aImage);
    172    if (!surface) {
    173      return NS_ERROR_FAILURE;
    174    }
    175 
    176    RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
    177    if (!dataSurface) {
    178      return NS_ERROR_FAILURE;
    179    }
    180 
    181    surfaceMap.emplace(dataSurface, DataSourceSurface::READ);
    182    if (!surfaceMap->IsMapped()) {
    183      return NS_ERROR_FAILURE;
    184    }
    185 
    186    surfaceFormat = dataSurface->GetFormat();
    187    switch (surfaceFormat) {
    188      case SurfaceFormat::B8G8R8A8:
    189      case SurfaceFormat::B8G8R8X8:
    190        if (!needsScale) {
    191          return MapRv(
    192              libyuv::ARGBToI420(static_cast<uint8_t*>(surfaceMap->GetData()),
    193                                 surfaceMap->GetStride(), aDestY, aDestStrideY,
    194                                 aDestU, aDestStrideU, aDestV, aDestStrideV,
    195                                 aDestSize.width, aDestSize.height));
    196        }
    197        break;
    198      case SurfaceFormat::R8G8B8A8:
    199      case SurfaceFormat::R8G8B8X8:
    200        if (!needsScale) {
    201          return MapRv(
    202              libyuv::ABGRToI420(static_cast<uint8_t*>(surfaceMap->GetData()),
    203                                 surfaceMap->GetStride(), aDestY, aDestStrideY,
    204                                 aDestU, aDestStrideU, aDestV, aDestStrideV,
    205                                 aDestSize.width, aDestSize.height));
    206        }
    207        break;
    208      case SurfaceFormat::R5G6B5_UINT16:
    209        if (!needsScale) {
    210          return MapRv(libyuv::RGB565ToI420(
    211              static_cast<uint8_t*>(surfaceMap->GetData()),
    212              surfaceMap->GetStride(), aDestY, aDestStrideY, aDestU,
    213              aDestStrideU, aDestV, aDestStrideV, aDestSize.width,
    214              aDestSize.height));
    215        }
    216        earlyScale = false;
    217        break;
    218      default:
    219        MOZ_ASSERT_UNREACHABLE("Surface format conversion not implemented");
    220        return NS_ERROR_NOT_IMPLEMENTED;
    221    }
    222  }
    223 
    224  MOZ_DIAGNOSTIC_ASSERT(needsScale);
    225 
    226  // We have to scale, and we are unable to scale directly, so we need a
    227  // temporary buffer to hold the scaled result in the input format, or the
    228  // unscaled result in the output format.
    229  IntSize tempBufSize;
    230  IntSize tempBufCbCrSize;
    231  if (earlyScale) {
    232    // Early scaling means we are scaling from the input buffer to a temporary
    233    // buffer of the same format.
    234    tempBufSize = aDestSize;
    235    if (data) {
    236      tempBufCbCrSize = gfx::ChromaSize(tempBufSize, data->mChromaSubsampling);
    237    }
    238  } else {
    239    // Late scaling means we are scaling from a temporary I420 buffer to the
    240    // destination I420 buffer.
    241    tempBufSize = imageSize;
    242    tempBufCbCrSize = gfx::ChromaSize(
    243        tempBufSize, gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT);
    244  }
    245 
    246  MOZ_ASSERT(!tempBufSize.IsEmpty());
    247 
    248  // Make sure we can allocate the temporary buffer.
    249  gfx::AlignedArray<uint8_t> tempBuf;
    250  uint8_t* tempBufY = nullptr;
    251  uint8_t* tempBufU = nullptr;
    252  uint8_t* tempBufV = nullptr;
    253  int32_t tempRgbStride = 0;
    254  if (!tempBufCbCrSize.IsEmpty()) {
    255    // Our temporary buffer is represented as a YUV format.
    256    auto tempBufYLen =
    257        CheckedInt<size_t>(tempBufSize.width) * tempBufSize.height;
    258    auto tempBufCbCrLen =
    259        CheckedInt<size_t>(tempBufCbCrSize.width) * tempBufCbCrSize.height;
    260    auto tempBufLen = tempBufYLen + 2 * tempBufCbCrLen;
    261    if (!tempBufLen.isValid()) {
    262      MOZ_ASSERT_UNREACHABLE("Bad buffer size!");
    263      return NS_ERROR_FAILURE;
    264    }
    265 
    266    tempBuf.Realloc(tempBufLen.value());
    267    if (!tempBuf) {
    268      return NS_ERROR_OUT_OF_MEMORY;
    269    }
    270 
    271    tempBufY = tempBuf;
    272    tempBufU = tempBufY + tempBufYLen.value();
    273    tempBufV = tempBufU + tempBufCbCrLen.value();
    274  } else {
    275    // The temporary buffer is represented as a RGBA/BGRA format.
    276    auto tempStride = CheckedInt<int32_t>(tempBufSize.width) * 4;
    277    auto tempBufLen = tempStride * tempBufSize.height;
    278    if (!tempStride.isValid() || !tempBufLen.isValid()) {
    279      MOZ_ASSERT_UNREACHABLE("Bad buffer size!");
    280      return NS_ERROR_FAILURE;
    281    }
    282 
    283    tempBuf.Realloc(tempBufLen.value());
    284    if (!tempBuf) {
    285      return NS_ERROR_OUT_OF_MEMORY;
    286    }
    287 
    288    tempRgbStride = tempStride.value();
    289  }
    290 
    291  nsresult rv;
    292  if (!earlyScale) {
    293    // First convert whatever the input format is to I420 into the temp buffer.
    294    if (data) {
    295      switch (format.value()) {
    296        case ImageBitmapFormat::YUV422P:
    297          rv = MapRv(libyuv::I422ToI420(
    298              data->mYChannel, data->mYStride, data->mCbChannel,
    299              data->mCbCrStride, data->mCrChannel, data->mCbCrStride, tempBufY,
    300              tempBufSize.width, tempBufU, tempBufCbCrSize.width, tempBufV,
    301              tempBufCbCrSize.width, tempBufSize.width, tempBufSize.height));
    302          break;
    303        case ImageBitmapFormat::YUV444P:
    304          rv = MapRv(libyuv::I444ToI420(
    305              data->mYChannel, data->mYStride, data->mCbChannel,
    306              data->mCbCrStride, data->mCrChannel, data->mCbCrStride, tempBufY,
    307              tempBufSize.width, tempBufU, tempBufCbCrSize.width, tempBufV,
    308              tempBufCbCrSize.width, tempBufSize.width, tempBufSize.height));
    309          break;
    310        case ImageBitmapFormat::YUV420SP_NV12:
    311          rv = MapRv(libyuv::NV12ToI420(
    312              data->mYChannel, data->mYStride, data->mCbChannel,
    313              data->mCbCrStride, tempBufY, tempBufSize.width, tempBufU,
    314              tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width,
    315              tempBufSize.width, tempBufSize.height));
    316          break;
    317        case ImageBitmapFormat::YUV420SP_NV21:
    318          rv = MapRv(libyuv::NV21ToI420(
    319              data->mYChannel, data->mYStride, data->mCrChannel,
    320              data->mCbCrStride, tempBufY, tempBufSize.width, tempBufU,
    321              tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width,
    322              tempBufSize.width, tempBufSize.height));
    323          break;
    324        default:
    325          MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented");
    326          return NS_ERROR_UNEXPECTED;
    327      }
    328    } else {
    329      switch (surfaceFormat) {
    330        case SurfaceFormat::B8G8R8A8:
    331        case SurfaceFormat::B8G8R8X8:
    332          rv = MapRv(libyuv::ARGBToI420(
    333              static_cast<uint8_t*>(surfaceMap->GetData()),
    334              surfaceMap->GetStride(), tempBufY, tempBufSize.width, tempBufU,
    335              tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width,
    336              tempBufSize.width, tempBufSize.height));
    337          break;
    338        case SurfaceFormat::R8G8B8A8:
    339        case SurfaceFormat::R8G8B8X8:
    340          rv = MapRv(libyuv::ABGRToI420(
    341              static_cast<uint8_t*>(surfaceMap->GetData()),
    342              surfaceMap->GetStride(), tempBufY, tempBufSize.width, tempBufU,
    343              tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width,
    344              tempBufSize.width, tempBufSize.height));
    345          break;
    346        case SurfaceFormat::R5G6B5_UINT16:
    347          rv = MapRv(libyuv::RGB565ToI420(
    348              static_cast<uint8_t*>(surfaceMap->GetData()),
    349              surfaceMap->GetStride(), tempBufY, tempBufSize.width, tempBufU,
    350              tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width,
    351              tempBufSize.width, tempBufSize.height));
    352          break;
    353        default:
    354          MOZ_ASSERT_UNREACHABLE("Surface format conversion not implemented");
    355          return NS_ERROR_NOT_IMPLEMENTED;
    356      }
    357    }
    358 
    359    if (NS_FAILED(rv)) {
    360      return rv;
    361    }
    362 
    363    // Now do the scale in I420 to the output buffer.
    364    return MapRv(libyuv::I420Scale(
    365        tempBufY, tempBufSize.width, tempBufU, tempBufCbCrSize.width, tempBufV,
    366        tempBufCbCrSize.width, tempBufSize.width, tempBufSize.height, aDestY,
    367        aDestStrideY, aDestU, aDestStrideU, aDestV, aDestStrideV,
    368        aDestSize.width, aDestSize.height, libyuv::FilterMode::kFilterBox));
    369  }
    370 
    371  if (data) {
    372    // First scale in the input format to the desired size into temp buffer, and
    373    // then convert that into the final I420 result.
    374    switch (format.value()) {
    375      case ImageBitmapFormat::YUV422P:
    376        rv = MapRv(libyuv::I422Scale(
    377            data->mYChannel, data->mYStride, data->mCbChannel,
    378            data->mCbCrStride, data->mCrChannel, data->mCbCrStride,
    379            imageSize.width, imageSize.height, tempBufY, tempBufSize.width,
    380            tempBufU, tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width,
    381            tempBufSize.width, tempBufSize.height,
    382            libyuv::FilterMode::kFilterBox));
    383        if (NS_FAILED(rv)) {
    384          return rv;
    385        }
    386        return MapRv(libyuv::I422ToI420(
    387            tempBufY, tempBufSize.width, tempBufU, tempBufCbCrSize.width,
    388            tempBufV, tempBufCbCrSize.width, aDestY, aDestStrideY, aDestU,
    389            aDestStrideU, aDestV, aDestStrideV, aDestSize.width,
    390            aDestSize.height));
    391      case ImageBitmapFormat::YUV444P:
    392        rv = MapRv(libyuv::I444Scale(
    393            data->mYChannel, data->mYStride, data->mCbChannel,
    394            data->mCbCrStride, data->mCrChannel, data->mCbCrStride,
    395            imageSize.width, imageSize.height, tempBufY, tempBufSize.width,
    396            tempBufU, tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width,
    397            tempBufSize.width, tempBufSize.height,
    398            libyuv::FilterMode::kFilterBox));
    399        if (NS_FAILED(rv)) {
    400          return rv;
    401        }
    402        return MapRv(libyuv::I444ToI420(
    403            tempBufY, tempBufSize.width, tempBufU, tempBufCbCrSize.width,
    404            tempBufV, tempBufCbCrSize.width, aDestY, aDestStrideY, aDestU,
    405            aDestStrideU, aDestV, aDestStrideV, aDestSize.width,
    406            aDestSize.height));
    407      case ImageBitmapFormat::YUV420SP_NV12:
    408        rv = MapRv(libyuv::NV12Scale(
    409            data->mYChannel, data->mYStride, data->mCbChannel,
    410            data->mCbCrStride, imageSize.width, imageSize.height, tempBufY,
    411            tempBufSize.width, tempBufU, tempBufCbCrSize.width,
    412            tempBufSize.width, tempBufSize.height,
    413            libyuv::FilterMode::kFilterBox));
    414        if (NS_FAILED(rv)) {
    415          return rv;
    416        }
    417        return MapRv(libyuv::NV12ToI420(
    418            tempBufY, tempBufSize.width, tempBufU, tempBufCbCrSize.width,
    419            aDestY, aDestStrideY, aDestU, aDestStrideU, aDestV, aDestStrideV,
    420            aDestSize.width, aDestSize.height));
    421      default:
    422        MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented");
    423        return NS_ERROR_NOT_IMPLEMENTED;
    424    }
    425  }
    426 
    427  MOZ_DIAGNOSTIC_ASSERT(surfaceFormat == SurfaceFormat::B8G8R8X8 ||
    428                        surfaceFormat == SurfaceFormat::B8G8R8A8 ||
    429                        surfaceFormat == SurfaceFormat::R8G8B8X8 ||
    430                        surfaceFormat == SurfaceFormat::R8G8B8A8);
    431 
    432  // We can use the same scaling method for either BGRA or RGBA since the
    433  // channel orders don't matter to the scaling algorithm.
    434  rv = MapRv(libyuv::ARGBScale(
    435      surfaceMap->GetData(), surfaceMap->GetStride(), imageSize.width,
    436      imageSize.height, tempBuf, tempRgbStride, tempBufSize.width,
    437      tempBufSize.height, libyuv::FilterMode::kFilterBox));
    438  if (NS_FAILED(rv)) {
    439    return rv;
    440  }
    441 
    442  // Now convert the scale result to I420.
    443  if (surfaceFormat == SurfaceFormat::B8G8R8A8 ||
    444      surfaceFormat == SurfaceFormat::B8G8R8X8) {
    445    return MapRv(libyuv::ARGBToI420(
    446        tempBuf, tempRgbStride, aDestY, aDestStrideY, aDestU, aDestStrideU,
    447        aDestV, aDestStrideV, aDestSize.width, aDestSize.height));
    448  }
    449 
    450  return MapRv(libyuv::ABGRToI420(tempBuf, tempRgbStride, aDestY, aDestStrideY,
    451                                  aDestU, aDestStrideU, aDestV, aDestStrideV,
    452                                  aDestSize.width, aDestSize.height));
    453 }
    454 
    455 static int32_t CeilingOfHalf(int32_t aValue) {
    456  MOZ_ASSERT(aValue >= 0);
    457  return aValue / 2 + (aValue % 2);
    458 }
    459 
    460 nsresult ConvertToNV12(layers::Image* aImage, uint8_t* aDestY, int aDestStrideY,
    461                       uint8_t* aDestUV, int aDestStrideUV,
    462                       gfx::IntSize aDestSize) {
    463  if (!aImage->IsValid()) {
    464    return NS_ERROR_INVALID_ARG;
    465  }
    466 
    467  if (const PlanarYCbCrData* data = GetPlanarYCbCrData(aImage)) {
    468    const ImageUtils imageUtils(aImage);
    469    Maybe<dom::ImageBitmapFormat> format = imageUtils.GetFormat();
    470    if (format.isNothing()) {
    471      MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented");
    472      return NS_ERROR_NOT_IMPLEMENTED;
    473    }
    474 
    475    if (format.value() != ImageBitmapFormat::YUV420P) {
    476      NS_WARNING("ConvertToNV12: Convert YUV data in I420 only");
    477      return NS_ERROR_NOT_IMPLEMENTED;
    478    }
    479 
    480    PlanarYCbCrData i420Source = *data;
    481    gfx::AlignedArray<uint8_t> scaledI420Buffer;
    482 
    483    if (aDestSize != aImage->GetSize()) {
    484      const int32_t halfWidth = CeilingOfHalf(aDestSize.width);
    485      const uint32_t halfHeight = CeilingOfHalf(aDestSize.height);
    486 
    487      CheckedInt<size_t> ySize(aDestSize.width);
    488      ySize *= aDestSize.height;
    489 
    490      CheckedInt<size_t> uSize(halfWidth);
    491      uSize *= halfHeight;
    492 
    493      CheckedInt<size_t> vSize(uSize);
    494 
    495      CheckedInt<size_t> i420Size = ySize + uSize + vSize;
    496      if (!i420Size.isValid()) {
    497        NS_WARNING("ConvertToNV12: Destination size is too large");
    498        return NS_ERROR_INVALID_ARG;
    499      }
    500 
    501      scaledI420Buffer.Realloc(i420Size.value());
    502      if (!scaledI420Buffer) {
    503        NS_WARNING(
    504            "ConvertToNV12: Failed to allocate buffer for rescaled I420 image");
    505        return NS_ERROR_OUT_OF_MEMORY;
    506      }
    507 
    508      // Y plane
    509      i420Source.mYChannel = scaledI420Buffer;
    510      i420Source.mYStride = aDestSize.width;
    511      i420Source.mYSkip = 0;
    512 
    513      // Cb plane (aka U)
    514      i420Source.mCbChannel = i420Source.mYChannel + ySize.value();
    515      i420Source.mCbSkip = 0;
    516 
    517      // Cr plane (aka V)
    518      i420Source.mCrChannel = i420Source.mCbChannel + uSize.value();
    519      i420Source.mCrSkip = 0;
    520 
    521      i420Source.mCbCrStride = halfWidth;
    522      i420Source.mChromaSubsampling =
    523          gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
    524      i420Source.mPictureRect = {0, 0, aDestSize.width, aDestSize.height};
    525 
    526      nsresult rv = MapRv(libyuv::I420Scale(
    527          data->mYChannel, data->mYStride, data->mCbChannel, data->mCbCrStride,
    528          data->mCrChannel, data->mCbCrStride, aImage->GetSize().width,
    529          aImage->GetSize().height, i420Source.mYChannel, i420Source.mYStride,
    530          i420Source.mCbChannel, i420Source.mCbCrStride, i420Source.mCrChannel,
    531          i420Source.mCbCrStride, i420Source.mPictureRect.width,
    532          i420Source.mPictureRect.height, libyuv::FilterMode::kFilterBox));
    533      if (NS_FAILED(rv)) {
    534        NS_WARNING("ConvertToNV12: I420Scale failed");
    535        return rv;
    536      }
    537    }
    538 
    539    return MapRv(libyuv::I420ToNV12(
    540        i420Source.mYChannel, i420Source.mYStride, i420Source.mCbChannel,
    541        i420Source.mCbCrStride, i420Source.mCrChannel, i420Source.mCbCrStride,
    542        aDestY, aDestStrideY, aDestUV, aDestStrideUV, aDestSize.width,
    543        aDestSize.height));
    544  }
    545 
    546  RefPtr<SourceSurface> surf = GetSourceSurface(aImage);
    547  if (!surf) {
    548    return NS_ERROR_FAILURE;
    549  }
    550 
    551  RefPtr<DataSourceSurface> data = surf->GetDataSurface();
    552  if (!data) {
    553    return NS_ERROR_FAILURE;
    554  }
    555 
    556  DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ);
    557  if (!map.IsMapped()) {
    558    return NS_ERROR_FAILURE;
    559  }
    560 
    561  if (surf->GetFormat() != SurfaceFormat::B8G8R8A8 &&
    562      surf->GetFormat() != SurfaceFormat::B8G8R8X8) {
    563    NS_WARNING("ConvertToNV12: Convert SurfaceFormat in BGR* only");
    564    return NS_ERROR_NOT_IMPLEMENTED;
    565  }
    566 
    567  struct RgbSource {
    568    uint8_t* mBuffer;
    569    int32_t mStride;
    570  } rgbSource = {map.GetData(), map.GetStride()};
    571 
    572  gfx::AlignedArray<uint8_t> scaledRGB32Buffer;
    573 
    574  if (aDestSize != aImage->GetSize()) {
    575    CheckedInt<int> rgbaStride(aDestSize.width);
    576    rgbaStride *= 4;
    577    if (!rgbaStride.isValid()) {
    578      NS_WARNING("ConvertToNV12: Destination width is too large");
    579      return NS_ERROR_INVALID_ARG;
    580    }
    581 
    582    CheckedInt<size_t> rgbSize(rgbaStride.value());
    583    rgbSize *= aDestSize.height;
    584    if (!rgbSize.isValid()) {
    585      NS_WARNING("ConvertToNV12: Destination size is too large");
    586      return NS_ERROR_INVALID_ARG;
    587    }
    588 
    589    scaledRGB32Buffer.Realloc(rgbSize.value());
    590    if (!scaledRGB32Buffer) {
    591      NS_WARNING(
    592          "ConvertToNV12: Failed to allocate buffer for rescaled RGB32 image");
    593      return NS_ERROR_OUT_OF_MEMORY;
    594    }
    595 
    596    nsresult rv = MapRv(libyuv::ARGBScale(
    597        map.GetData(), map.GetStride(), aImage->GetSize().width,
    598        aImage->GetSize().height, scaledRGB32Buffer, rgbaStride.value(),
    599        aDestSize.width, aDestSize.height, libyuv::FilterMode::kFilterBox));
    600    if (NS_FAILED(rv)) {
    601      NS_WARNING("ConvertToNV12: ARGBScale failed");
    602      return rv;
    603    }
    604 
    605    rgbSource.mBuffer = scaledRGB32Buffer;
    606    rgbSource.mStride = rgbaStride.value();
    607  }
    608 
    609  return MapRv(libyuv::ARGBToNV12(rgbSource.mBuffer, rgbSource.mStride, aDestY,
    610                                  aDestStrideY, aDestUV, aDestStrideUV,
    611                                  aDestSize.width, aDestSize.height));
    612 }
    613 
    614 static bool IsRGBX(const SurfaceFormat& aFormat) {
    615  return aFormat == SurfaceFormat::B8G8R8A8 ||
    616         aFormat == SurfaceFormat::B8G8R8X8 ||
    617         aFormat == SurfaceFormat::R8G8B8A8 ||
    618         aFormat == SurfaceFormat::R8G8B8X8 ||
    619         aFormat == SurfaceFormat::X8R8G8B8 ||
    620         aFormat == SurfaceFormat::A8R8G8B8;
    621 }
    622 
    623 static bool HasAlpha(const SurfaceFormat& aFormat) {
    624  return aFormat == SurfaceFormat::B8G8R8A8 ||
    625         aFormat == SurfaceFormat::R8G8B8A8 ||
    626         aFormat == SurfaceFormat::A8R8G8B8;
    627 }
    628 
    629 static nsresult SwapRGBA(DataSourceSurface* aSurface,
    630                         const SurfaceFormat& aDestFormat) {
    631  if (!aSurface || !IsRGBX(aSurface->GetFormat()) || !IsRGBX(aDestFormat)) {
    632    return NS_ERROR_INVALID_ARG;
    633  }
    634 
    635  if (aSurface->GetFormat() == aDestFormat) {
    636    return NS_OK;
    637  }
    638 
    639  DataSourceSurface::ScopedMap map(aSurface, DataSourceSurface::READ_WRITE);
    640  if (!map.IsMapped()) {
    641    return NS_ERROR_FAILURE;
    642  }
    643 
    644  gfx::SwizzleData(map.GetData(), map.GetStride(), aSurface->GetFormat(),
    645                   map.GetData(), map.GetStride(), aDestFormat,
    646                   aSurface->GetSize());
    647 
    648  return NS_OK;
    649 }
    650 
    651 nsresult ConvertToRGBA(Image* aImage, const SurfaceFormat& aDestFormat,
    652                       uint8_t* aDestBuffer, int aDestStride) {
    653  if (!aImage || !aImage->IsValid() || aImage->GetSize().IsEmpty() ||
    654      !aDestBuffer || !IsRGBX(aDestFormat) || aDestStride <= 0) {
    655    return NS_ERROR_INVALID_ARG;
    656  }
    657 
    658  // Read YUV image to the given buffer in required RGBA format.
    659  if (const PlanarYCbCrData* data = GetPlanarYCbCrData(aImage)) {
    660    SurfaceFormat convertedFormat = aDestFormat;
    661    gfx::PremultFunc premultOp = nullptr;
    662    if (data->mAlpha && HasAlpha(aDestFormat)) {
    663      if (aDestFormat == SurfaceFormat::A8R8G8B8) {
    664        convertedFormat = SurfaceFormat::B8G8R8A8;
    665      }
    666      if (data->mAlpha->mPremultiplied) {
    667        premultOp = libyuv::ARGBUnattenuate;
    668      }
    669    } else {
    670      if (aDestFormat == SurfaceFormat::X8R8G8B8 ||
    671          aDestFormat == SurfaceFormat::A8R8G8B8) {
    672        convertedFormat = SurfaceFormat::B8G8R8X8;
    673      }
    674    }
    675 
    676    nsresult result =
    677        ConvertYCbCrToRGB32(*data, convertedFormat, aDestBuffer,
    678                            AssertedCast<int32_t>(aDestStride), premultOp);
    679    if (NS_FAILED(result)) {
    680      return result;
    681    }
    682 
    683    if (convertedFormat == aDestFormat) {
    684      return NS_OK;
    685    }
    686 
    687    // Since format of the converted data returned from ConvertYCbCrToRGB or
    688    // ConvertYCbCrAToARGB is BRG* or RBG*, we need swap the RGBA channels to
    689    // the required format if needed.
    690 
    691    RefPtr<DataSourceSurface> surf =
    692        gfx::Factory::CreateWrappingDataSourceSurface(
    693            aDestBuffer, aDestStride, aImage->GetSize(), convertedFormat);
    694 
    695    if (!surf) {
    696      return NS_ERROR_FAILURE;
    697    }
    698 
    699    return SwapRGBA(surf.get(), aDestFormat);
    700  }
    701 
    702  // Read RGBA image to the given buffer in required RGBA format.
    703 
    704  RefPtr<SourceSurface> surf = GetSourceSurface(aImage);
    705  if (!surf) {
    706    return NS_ERROR_FAILURE;
    707  }
    708 
    709  if (!IsRGBX(surf->GetFormat())) {
    710    return NS_ERROR_NOT_IMPLEMENTED;
    711  }
    712 
    713  RefPtr<DataSourceSurface> src = surf->GetDataSurface();
    714  if (!src) {
    715    return NS_ERROR_FAILURE;
    716  }
    717 
    718  DataSourceSurface::ScopedMap srcMap(src, DataSourceSurface::READ);
    719  if (!srcMap.IsMapped()) {
    720    return NS_ERROR_FAILURE;
    721  }
    722 
    723  RefPtr<DataSourceSurface> dest =
    724      gfx::Factory::CreateWrappingDataSourceSurface(
    725          aDestBuffer, aDestStride, aImage->GetSize(), aDestFormat);
    726 
    727  if (!dest) {
    728    return NS_ERROR_FAILURE;
    729  }
    730 
    731  DataSourceSurface::ScopedMap destMap(dest, gfx::DataSourceSurface::WRITE);
    732  if (!destMap.IsMapped()) {
    733    return NS_ERROR_FAILURE;
    734  }
    735 
    736  gfx::SwizzleData(srcMap.GetData(), srcMap.GetStride(), src->GetFormat(),
    737                   destMap.GetData(), destMap.GetStride(), dest->GetFormat(),
    738                   dest->GetSize());
    739 
    740  return NS_OK;
    741 }
    742 
    743 static SkColorType ToSkColorType(const SurfaceFormat& aFormat) {
    744  switch (aFormat) {
    745    case SurfaceFormat::B8G8R8A8:
    746    case SurfaceFormat::B8G8R8X8:
    747      return kBGRA_8888_SkColorType;
    748    case SurfaceFormat::R8G8B8A8:
    749    case SurfaceFormat::R8G8B8X8:
    750      return kRGBA_8888_SkColorType;
    751    default:
    752      break;
    753  }
    754  return kUnknown_SkColorType;
    755 }
    756 
    757 nsresult ConvertSRGBBufferToDisplayP3(uint8_t* aSrcBuffer,
    758                                      const SurfaceFormat& aSrcFormat,
    759                                      uint8_t* aDestBuffer, int aWidth,
    760                                      int aHeight) {
    761  if (!aSrcBuffer || !aDestBuffer || aWidth <= 0 || aHeight <= 0 ||
    762      !IsRGBX(aSrcFormat)) {
    763    return NS_ERROR_INVALID_ARG;
    764  }
    765 
    766  SkColorType srcColorType = ToSkColorType(aSrcFormat);
    767  if (srcColorType == kUnknown_SkColorType) {
    768    return NS_ERROR_NOT_IMPLEMENTED;
    769  }
    770 
    771  // TODO: Provide source's color space info to customize SkColorSpace.
    772  auto srcColorSpace = SkColorSpace::MakeSRGB();
    773  SkImageInfo srcInfo = SkImageInfo::Make(aWidth, aHeight, srcColorType,
    774                                          kUnpremul_SkAlphaType, srcColorSpace);
    775 
    776  constexpr size_t bytesPerPixel = 4;
    777  CheckedInt<size_t> rowBytes(bytesPerPixel);
    778  rowBytes *= aWidth;
    779  if (!rowBytes.isValid()) {
    780    return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
    781  }
    782 
    783  SkBitmap srcBitmap;
    784  if (!srcBitmap.installPixels(srcInfo, aSrcBuffer, rowBytes.value())) {
    785    return NS_ERROR_FAILURE;
    786  }
    787 
    788  // TODO: Provide destination's color space info to customize SkColorSpace.
    789  auto destColorSpace =
    790      SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3);
    791 
    792  SkBitmap destBitmap;
    793  if (!destBitmap.tryAllocPixels(srcInfo.makeColorSpace(destColorSpace))) {
    794    return NS_ERROR_FAILURE;
    795  }
    796 
    797  if (!srcBitmap.readPixels(destBitmap.pixmap())) {
    798    return NS_ERROR_FAILURE;
    799  }
    800 
    801  CheckedInt<size_t> size(rowBytes.value());
    802  size *= aHeight;
    803  if (!size.isValid()) {
    804    return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
    805  }
    806 
    807  PodCopy(aDestBuffer, reinterpret_cast<uint8_t*>(destBitmap.getPixels()),
    808          size.value());
    809  return NS_OK;
    810 }
    811 
    812 }  // namespace mozilla