/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "ImageDataSerializer.h"

#include "YCbCrUtils.h"           // for YCbCr conversions
#include "gfx2DGlue.h"            // for SurfaceFormatToImageFormat
#include "mozilla/Assertions.h"   // for MOZ_ASSERT, etc
#include "mozilla/DebugOnly.h"    // for DebugOnly
#include "mozilla/gfx/2D.h"       // for DataSourceSurface, Factory
#include "mozilla/gfx/Logging.h"  // for gfxDebug
#include "mozilla/gfx/Tools.h"    // for GetAlignedStride, etc
#include "mozilla/gfx/Types.h"
#include "mozilla/mozalloc.h"  // for operator delete, etc

namespace mozilla {
namespace layers {
namespace ImageDataSerializer {

using namespace gfx;

int32_t ComputeRGBStride(SurfaceFormat aFormat, int32_t aWidth) {
#ifdef XP_MACOSX
  // Some drivers require an alignment of 32 bytes for efficient texture upload.
  return GetAlignedStride<32>(aWidth, BytesPerPixel(aFormat));
#else
  return GetAlignedStride<4>(aWidth, BytesPerPixel(aFormat));
#endif
}

int32_t GetRGBStride(const RGBDescriptor& aDescriptor) {
  return ComputeRGBStride(aDescriptor.format(), aDescriptor.size().width);
}

uint32_t ComputeRGBBufferSize(IntSize aSize, SurfaceFormat aFormat) {
  MOZ_ASSERT(aSize.height >= 0 && aSize.width >= 0);

  // This takes care of checking whether there could be overflow
  // with enough margin for the metadata.
  if (!gfx::Factory::AllowedSurfaceSize(aSize)) {
    return 0;
  }

  // Note we're passing height instad of the bpp parameter, but the end
  // result is the same - and the bpp was already taken care of in the
  // ComputeRGBStride function.
  int32_t bufsize = GetAlignedStride<16>(ComputeRGBStride(aFormat, aSize.width),
                                         aSize.height);

  if (bufsize < 0) {
    // This should not be possible thanks to Factory::AllowedSurfaceSize
    return 0;
  }

  return bufsize;
}

// Minimum required shmem size in bytes
uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride,
                                const gfx::IntSize& aCbCrSize,
                                int32_t aCbCrStride) {
  MOZ_ASSERT(aYSize.height >= 0 && aYSize.width >= 0);

  if (aYSize.height < 0 || aYSize.width < 0 || aCbCrSize.height < 0 ||
      aCbCrSize.width < 0 ||
      !gfx::Factory::AllowedSurfaceSize(IntSize(aYStride, aYSize.height)) ||
      !gfx::Factory::AllowedSurfaceSize(
          IntSize(aCbCrStride, aCbCrSize.height))) {
    return 0;
  }

  // Overflow checks are performed in AllowedSurfaceSize
  return GetAlignedStride<4>(aYSize.height, aYStride) +
         2 * GetAlignedStride<4>(aCbCrSize.height, aCbCrStride);
}

uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride,
                                const gfx::IntSize& aCbCrSize,
                                int32_t aCbCrStride, uint32_t aYOffset,
                                uint32_t aCbOffset, uint32_t aCrOffset) {
  MOZ_ASSERT(aYSize.height >= 0 && aYSize.width >= 0);

  if (aYSize.height < 0 || aYSize.width < 0 || aCbCrSize.height < 0 ||
      aCbCrSize.width < 0 ||
      !gfx::Factory::AllowedSurfaceSize(IntSize(aYStride, aYSize.height)) ||
      !gfx::Factory::AllowedSurfaceSize(
          IntSize(aCbCrStride, aCbCrSize.height))) {
    return 0;
  }

  uint32_t yLength = GetAlignedStride<4>(aYStride, aYSize.height);
  uint32_t cbCrLength = GetAlignedStride<4>(aCbCrStride, aCbCrSize.height);
  if (yLength == 0 || cbCrLength == 0) {
    return 0;
  }

  CheckedInt<uint32_t> yEnd = aYOffset;
  yEnd += yLength;
  CheckedInt<uint32_t> cbEnd = aCbOffset;
  cbEnd += cbCrLength;
  CheckedInt<uint32_t> crEnd = aCrOffset;
  crEnd += cbCrLength;

  if (!yEnd.isValid() || !cbEnd.isValid() || !crEnd.isValid() ||
      yEnd.value() > aCbOffset || cbEnd.value() > aCrOffset) {
    return 0;
  }

  return crEnd.value();
}

uint32_t ComputeYCbCrBufferSize(uint32_t aBufferSize) {
  return GetAlignedStride<4>(aBufferSize, 1);
}

void ComputeYCbCrOffsets(int32_t yStride, int32_t yHeight, int32_t cbCrStride,
                         int32_t cbCrHeight, uint32_t& outYOffset,
                         uint32_t& outCbOffset, uint32_t& outCrOffset) {
  outYOffset = 0;
  outCbOffset = outYOffset + GetAlignedStride<4>(yStride, yHeight);
  outCrOffset = outCbOffset + GetAlignedStride<4>(cbCrStride, cbCrHeight);
}

gfx::SurfaceFormat FormatFromBufferDescriptor(
    const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return aDescriptor.get_RGBDescriptor().format();
    case BufferDescriptor::TYCbCrDescriptor:
      return gfx::SurfaceFormat::YUV420;
    default:
      MOZ_CRASH("GFX: FormatFromBufferDescriptor");
  }
}

gfx::IntSize SizeFromBufferDescriptor(const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return aDescriptor.get_RGBDescriptor().size();
    case BufferDescriptor::TYCbCrDescriptor: {
      return aDescriptor.get_YCbCrDescriptor().display().Size();
    }
    default:
      MOZ_CRASH("GFX: SizeFromBufferDescriptor");
  }
}

gfx::IntRect RectFromBufferDescriptor(const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor: {
      auto size = aDescriptor.get_RGBDescriptor().size();
      return gfx::IntRect(0, 0, size.Width(), size.Height());
    }
    case BufferDescriptor::TYCbCrDescriptor:
      return aDescriptor.get_YCbCrDescriptor().display();
    default:
      MOZ_CRASH("GFX: RectFromBufferDescriptor");
  }
}

Maybe<gfx::IntSize> YSizeFromBufferDescriptor(
    const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return Nothing();
    case BufferDescriptor::TYCbCrDescriptor:
      return Some(aDescriptor.get_YCbCrDescriptor().ySize());
    default:
      MOZ_CRASH("GFX: YSizeFromBufferDescriptor");
  }
}

Maybe<gfx::IntSize> CbCrSizeFromBufferDescriptor(
    const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return Nothing();
    case BufferDescriptor::TYCbCrDescriptor:
      return Some(aDescriptor.get_YCbCrDescriptor().cbCrSize());
    default:
      MOZ_CRASH("GFX: CbCrSizeFromBufferDescriptor");
  }
}

Maybe<int32_t> YStrideFromBufferDescriptor(
    const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return Nothing();
    case BufferDescriptor::TYCbCrDescriptor:
      return Some(aDescriptor.get_YCbCrDescriptor().yStride());
    default:
      MOZ_CRASH("GFX: YStrideFromBufferDescriptor");
  }
}

Maybe<int32_t> CbCrStrideFromBufferDescriptor(
    const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return Nothing();
    case BufferDescriptor::TYCbCrDescriptor:
      return Some(aDescriptor.get_YCbCrDescriptor().cbCrStride());
    default:
      MOZ_CRASH("GFX: CbCrStrideFromBufferDescriptor");
  }
}

Maybe<gfx::YUVColorSpace> YUVColorSpaceFromBufferDescriptor(
    const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return Nothing();
    case BufferDescriptor::TYCbCrDescriptor:
      return Some(aDescriptor.get_YCbCrDescriptor().yUVColorSpace());
    default:
      MOZ_CRASH("GFX:  YUVColorSpaceFromBufferDescriptor");
  }
}

Maybe<gfx::ColorDepth> ColorDepthFromBufferDescriptor(
    const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return Nothing();
    case BufferDescriptor::TYCbCrDescriptor:
      return Some(aDescriptor.get_YCbCrDescriptor().colorDepth());
    default:
      MOZ_CRASH("GFX:  ColorDepthFromBufferDescriptor");
  }
}

Maybe<gfx::ColorRange> ColorRangeFromBufferDescriptor(
    const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return Nothing();
    case BufferDescriptor::TYCbCrDescriptor:
      return Some(aDescriptor.get_YCbCrDescriptor().colorRange());
    default:
      MOZ_CRASH("GFX: YUVFullRangeFromBufferDescriptor");
  }
}

Maybe<StereoMode> StereoModeFromBufferDescriptor(
    const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return Nothing();
    case BufferDescriptor::TYCbCrDescriptor:
      return Some(aDescriptor.get_YCbCrDescriptor().stereoMode());
    default:
      MOZ_CRASH("GFX:  StereoModeFromBufferDescriptor");
  }
}

Maybe<gfx::ChromaSubsampling> ChromaSubsamplingFromBufferDescriptor(
    const BufferDescriptor& aDescriptor) {
  switch (aDescriptor.type()) {
    case BufferDescriptor::TRGBDescriptor:
      return Nothing();
    case BufferDescriptor::TYCbCrDescriptor:
      return Some(aDescriptor.get_YCbCrDescriptor().chromaSubsampling());
    default:
      MOZ_CRASH("GFX: ChromaSubsamplingFromBufferDescriptor");
  }
}

uint8_t* GetYChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor) {
  return aBuffer + aDescriptor.yOffset();
}

uint8_t* GetCbChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor) {
  return aBuffer + aDescriptor.cbOffset();
}

uint8_t* GetCrChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor) {
  return aBuffer + aDescriptor.crOffset();
}

already_AddRefed<DataSourceSurface> DataSourceSurfaceFromYCbCrDescriptor(
    uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor,
    gfx::DataSourceSurface* aSurface) {
  const gfx::IntRect display = aDescriptor.display();
  const gfx::IntSize size = display.Size();
  RefPtr<DataSourceSurface> result;
  if (aSurface) {
    MOZ_ASSERT(aSurface->GetSize() == size);
    MOZ_ASSERT(aSurface->GetFormat() == gfx::SurfaceFormat::B8G8R8X8);
    if (aSurface->GetSize() == size &&
        aSurface->GetFormat() == gfx::SurfaceFormat::B8G8R8X8) {
      result = aSurface;
    }
  }

  if (!result) {
    result =
        Factory::CreateDataSourceSurface(size, gfx::SurfaceFormat::B8G8R8X8);
  }
  if (NS_WARN_IF(!result)) {
    return nullptr;
  }

  DataSourceSurface::MappedSurface map;
  if (NS_WARN_IF(!result->Map(DataSourceSurface::MapType::WRITE, &map))) {
    return nullptr;
  }

  layers::PlanarYCbCrData ycbcrData;
  ycbcrData.mYChannel = GetYChannel(aBuffer, aDescriptor);
  ycbcrData.mYStride = aDescriptor.yStride();
  ycbcrData.mCbChannel = GetCbChannel(aBuffer, aDescriptor);
  ycbcrData.mCrChannel = GetCrChannel(aBuffer, aDescriptor);
  ycbcrData.mCbCrStride = aDescriptor.cbCrStride();
  ycbcrData.mPictureRect = aDescriptor.display();
  ycbcrData.mYUVColorSpace = aDescriptor.yUVColorSpace();
  ycbcrData.mColorDepth = aDescriptor.colorDepth();
  ycbcrData.mChromaSubsampling = aDescriptor.chromaSubsampling();

  if (NS_WARN_IF(NS_FAILED(
          gfx::ConvertYCbCrToRGB(ycbcrData, gfx::SurfaceFormat::B8G8R8X8, size,
                                 map.mData, map.mStride)))) {
    MOZ_ASSERT_UNREACHABLE("Failed to convert YUV into RGB data");
    return nullptr;
  }

  result->Unmap();
  return result.forget();
}

void ConvertAndScaleFromYCbCrDescriptor(uint8_t* aBuffer,
                                        const YCbCrDescriptor& aDescriptor,
                                        const gfx::SurfaceFormat& aDestFormat,
                                        const gfx::IntSize& aDestSize,
                                        unsigned char* aDestBuffer,
                                        int32_t aStride) {
  MOZ_ASSERT(aBuffer);

  layers::PlanarYCbCrData ycbcrData;
  ycbcrData.mYChannel = GetYChannel(aBuffer, aDescriptor);
  ycbcrData.mYStride = aDescriptor.yStride();
  ycbcrData.mCbChannel = GetCbChannel(aBuffer, aDescriptor);
  ycbcrData.mCrChannel = GetCrChannel(aBuffer, aDescriptor);
  ycbcrData.mCbCrStride = aDescriptor.cbCrStride();
  ycbcrData.mPictureRect = aDescriptor.display();
  ycbcrData.mYUVColorSpace = aDescriptor.yUVColorSpace();
  ycbcrData.mColorDepth = aDescriptor.colorDepth();
  ycbcrData.mChromaSubsampling = aDescriptor.chromaSubsampling();

  DebugOnly<nsresult> result = gfx::ConvertYCbCrToRGB(
      ycbcrData, aDestFormat, aDestSize, aDestBuffer, aStride);
  MOZ_ASSERT(NS_SUCCEEDED(result), "Failed to convert YUV into RGB data");
}

gfx::IntSize GetCroppedCbCrSize(const YCbCrDescriptor& aDescriptor) {
  return ChromaSize(aDescriptor.display().Size(),
                    aDescriptor.chromaSubsampling());
}

}  // namespace ImageDataSerializer
}  // namespace layers
}  // namespace mozilla
