/* -*- Mode: C++; tab-width: 20; 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 "gfx2DGlue.h"

#include "YCbCrUtils.h"
#include "yuv_convert.h"
#include "ycbcr_to_rgb565.h"

namespace mozilla {
namespace gfx {

void
GetYCbCrToRGBDestFormatAndSize(const layers::PlanarYCbCrData& aData,
                               SurfaceFormat& aSuggestedFormat,
                               IntSize& aSuggestedSize)
{
  YUVType yuvtype =
    TypeFromSize(aData.mYSize.width,
                 aData.mYSize.height,
                 aData.mCbCrSize.width,
                 aData.mCbCrSize.height);

  // '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.mPicSize;

  if (aSuggestedFormat == SurfaceFormat::R5G6B5_UINT16) {
#if defined(HAVE_YCBCR_TO_RGB565)
    if (prescale &&
        !IsScaleYCbCrToRGB565Fast(aData.mPicX,
                                  aData.mPicY,
                                  aData.mPicSize.width,
                                  aData.mPicSize.height,
                                  aSuggestedSize.width,
                                  aSuggestedSize.height,
                                  yuvtype,
                                  FILTER_BILINEAR) &&
        IsConvertYCbCrToRGB565Fast(aData.mPicX,
                                   aData.mPicY,
                                   aData.mPicSize.width,
                                   aData.mPicSize.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.mPicX != 0 || aData.mPicY != 0 || yuvtype == YV24)
      prescale = false;
  }
  if (!prescale) {
    aSuggestedSize = aData.mPicSize;
  }
}

static inline void
ConvertYCbCr16to8Line(uint8_t* aDst,
                      int aStride,
                      const uint16_t* aSrc,
                      int aStride16,
                      int aWidth,
                      int aHeight,
                      int aBitDepth)
{
  uint16_t mask = (1 << aBitDepth) - 1;

  for (int i = 0; i < aHeight; i++) {
    for (int j = 0; j < aWidth; j++) {
      uint16_t val = (aSrc[j] & mask) >> (aBitDepth - 8);
      aDst[j] = val;
    }
    aDst += aStride;
    aSrc += aStride16;
  }
}

void
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.
  MOZ_ASSERT((aData.mCbCrSize.width == aData.mYSize.width ||
              aData.mCbCrSize.width == (aData.mYSize.width + 1) >> 1) &&
             (aData.mCbCrSize.height == aData.mYSize.height ||
              aData.mCbCrSize.height == (aData.mYSize.height + 1) >> 1));

  // Used if converting to 8 bits YUV.
  UniquePtr<uint8_t[]> yChannel;
  UniquePtr<uint8_t[]> cbChannel;
  UniquePtr<uint8_t[]> crChannel;
  layers::PlanarYCbCrData dstData;
  const layers::PlanarYCbCrData& srcData = aData.mBitDepth == 8 ? aData : dstData;

  if (aData.mBitDepth != 8) {
    MOZ_ASSERT(aData.mBitDepth > 8 && aData.mBitDepth <= 16);
    // Convert to 8 bits data first.
    dstData.mPicSize = aData.mPicSize;
    dstData.mPicX = aData.mPicX;
    dstData.mPicY = aData.mPicY;
    dstData.mYSize = aData.mYSize;
    // We align the destination stride to 32 bytes, so that libyuv can use
    // SSE optimised code.
    dstData.mYStride = (aData.mYSize.width + 31) & ~31;
    dstData.mCbCrSize = aData.mCbCrSize;
    dstData.mCbCrStride = (aData.mCbCrSize.width + 31) & ~31;
    dstData.mYUVColorSpace = aData.mYUVColorSpace;
    dstData.mBitDepth = 8;

    size_t ySize = GetAlignedStride<1>(dstData.mYStride, aData.mYSize.height);
    size_t cbcrSize =
      GetAlignedStride<1>(dstData.mCbCrStride, aData.mCbCrSize.height);
    if (ySize == 0 || cbcrSize == 0) {
      return;
    }
    yChannel = MakeUnique<uint8_t[]>(ySize);
    cbChannel = MakeUnique<uint8_t[]>(cbcrSize);
    crChannel = MakeUnique<uint8_t[]>(cbcrSize);

    dstData.mYChannel = yChannel.get();
    dstData.mCbChannel = cbChannel.get();
    dstData.mCrChannel = crChannel.get();

    ConvertYCbCr16to8Line(dstData.mYChannel,
                          dstData.mYStride,
                          reinterpret_cast<uint16_t*>(aData.mYChannel),
                          aData.mYStride / 2,
                          aData.mYSize.width,
                          aData.mYSize.height,
                          aData.mBitDepth);

    ConvertYCbCr16to8Line(dstData.mCbChannel,
                          dstData.mCbCrStride,
                          reinterpret_cast<uint16_t*>(aData.mCbChannel),
                          aData.mCbCrStride / 2,
                          aData.mCbCrSize.width,
                          aData.mCbCrSize.height,
                          aData.mBitDepth);

    ConvertYCbCr16to8Line(dstData.mCrChannel,
                          dstData.mCbCrStride,
                          reinterpret_cast<uint16_t*>(aData.mCrChannel),
                          aData.mCbCrStride / 2,
                          aData.mCbCrSize.width,
                          aData.mCbCrSize.height,
                          aData.mBitDepth);
  }

  YUVType yuvtype =
    TypeFromSize(srcData.mYSize.width,
                 srcData.mYSize.height,
                 srcData.mCbCrSize.width,
                 srcData.mCbCrSize.height);

  // Convert from YCbCr to RGB now, scaling the image if needed.
  if (aDestSize != srcData.mPicSize) {
#if defined(HAVE_YCBCR_TO_RGB565)
    if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) {
      ScaleYCbCrToRGB565(srcData.mYChannel,
                         srcData.mCbChannel,
                         srcData.mCrChannel,
                         aDestBuffer,
                         srcData.mPicX,
                         srcData.mPicY,
                         srcData.mPicSize.width,
                         srcData.mPicSize.height,
                         aDestSize.width,
                         aDestSize.height,
                         srcData.mYStride,
                         srcData.mCbCrStride,
                         aStride,
                         yuvtype,
                         FILTER_BILINEAR);
    } else
#endif
      ScaleYCbCrToRGB32(srcData.mYChannel, //
                        srcData.mCbChannel,
                        srcData.mCrChannel,
                        aDestBuffer,
                        srcData.mPicSize.width,
                        srcData.mPicSize.height,
                        aDestSize.width,
                        aDestSize.height,
                        srcData.mYStride,
                        srcData.mCbCrStride,
                        aStride,
                        yuvtype,
                        srcData.mYUVColorSpace,
                        FILTER_BILINEAR);
  } else { // no prescale
#if defined(HAVE_YCBCR_TO_RGB565)
    if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) {
      ConvertYCbCrToRGB565(srcData.mYChannel,
                           srcData.mCbChannel,
                           srcData.mCrChannel,
                           aDestBuffer,
                           srcData.mPicX,
                           srcData.mPicY,
                           srcData.mPicSize.width,
                           srcData.mPicSize.height,
                           srcData.mYStride,
                           srcData.mCbCrStride,
                           aStride,
                           yuvtype);
    } else // aDestFormat != SurfaceFormat::R5G6B5_UINT16
#endif
      ConvertYCbCrToRGB32(srcData.mYChannel, //
                          srcData.mCbChannel,
                          srcData.mCrChannel,
                          aDestBuffer,
                          srcData.mPicX,
                          srcData.mPicY,
                          srcData.mPicSize.width,
                          srcData.mPicSize.height,
                          srcData.mYStride,
                          srcData.mCbCrStride,
                          aStride,
                          yuvtype,
                          srcData.mYUVColorSpace);
  }
}

void
ConvertYCbCrAToARGB(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) {

  ConvertYCbCrAToARGB32(aSrcY,
                        aSrcU,
                        aSrcV,
                        aSrcA,
                        aDstARGB,
                        aWidth,
                        aHeight,
                        aSrcStrideYA,
                        aSrcStrideUV,
                        aDstStrideARGB);
}

} // namespace gfx
} // namespace mozilla
