/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * 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/. */

#ifndef GFX_UTILS_H
#define GFX_UTILS_H

#include "gfxMatrix.h"
#include "gfxRect.h"
#include "gfxTypes.h"
#include "ImageTypes.h"
#include "imgIContainer.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include "nsColor.h"
#include "nsContentUtils.h"
#include "nsPrintfCString.h"
#include "nsRegionFwd.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/webrender/WebRenderTypes.h"
#include "qcms.h"

class gfxASurface;
class gfxDrawable;
class gfxTextRun;
struct gfxQuad;
class nsICookieJarSettings;
class nsIInputStream;
class nsIGfxInfo;

namespace mozilla {
namespace dom {
class Element;
}  // namespace dom
namespace layers {
class WebRenderBridgeChild;
class GlyphArray;
struct PlanarYCbCrData;
class WebRenderCommand;
}  // namespace layers
namespace image {
class ImageRegion;
}  // namespace image
namespace wr {
class DisplayListBuilder;
}  // namespace wr
}  // namespace mozilla

enum class ImageType {
  BMP,
  ICO,
  JPEG,
  PNG,
};

class gfxUtils {
 public:
  typedef mozilla::gfx::DataSourceSurface DataSourceSurface;
  typedef mozilla::gfx::DrawTarget DrawTarget;
  typedef mozilla::gfx::IntPoint IntPoint;
  typedef mozilla::gfx::Matrix Matrix;
  typedef mozilla::gfx::Matrix4x4 Matrix4x4;
  typedef mozilla::gfx::SourceSurface SourceSurface;
  typedef mozilla::gfx::SurfaceFormat SurfaceFormat;
  typedef mozilla::image::ImageRegion ImageRegion;

  /*
   * Premultiply or Unpremultiply aSourceSurface, writing the result
   * to aDestSurface or back into aSourceSurface if aDestSurface is null.
   *
   * If aDestSurface is given, it must have identical format, dimensions, and
   * stride as the source.
   *
   * If the source is not SurfaceFormat::A8R8G8B8_UINT32, no operation is
   * performed.  If aDestSurface is given, the data is copied over.
   */
  static bool PremultiplyDataSurface(DataSourceSurface* srcSurf,
                                     DataSourceSurface* destSurf);
  static bool UnpremultiplyDataSurface(DataSourceSurface* srcSurf,
                                       DataSourceSurface* destSurf);

  static already_AddRefed<DataSourceSurface> CreatePremultipliedDataSurface(
      DataSourceSurface* srcSurf);
  static already_AddRefed<DataSourceSurface> CreateUnpremultipliedDataSurface(
      DataSourceSurface* srcSurf);

  static void ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength);

  /**
   * Draw something drawable while working around limitations like bad support
   * for EXTEND_PAD, lack of source-clipping, or cairo / pixman bugs with
   * extreme user-space-to-image-space transforms.
   *
   * The input parameters here usually come from the output of our image
   * snapping algorithm in nsLayoutUtils.cpp.
   * This method is split from nsLayoutUtils::DrawPixelSnapped to allow for
   * adjusting the parameters. For example, certain images with transparent
   * margins only have a drawable subimage. For those images, imgFrame::Draw
   * will tweak the rects and transforms that it gets from the pixel snapping
   * algorithm before passing them on to this method.
   */
  static void DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable,
                               const gfxSize& aImageSize,
                               const ImageRegion& aRegion,
                               const mozilla::gfx::SurfaceFormat aFormat,
                               mozilla::gfx::SamplingFilter aSamplingFilter,
                               uint32_t aImageFlags = imgIContainer::FLAG_NONE,
                               gfxFloat aOpacity = 1.0);

  /**
   * Clip aContext to the region aRegion.
   */
  static void ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion);

  /**
   * Clip aTarget to the region aRegion.
   */
  static void ClipToRegion(mozilla::gfx::DrawTarget* aTarget,
                           const nsIntRegion& aRegion);

  /*
   * Convert image format to depth value
   */
  static int ImageFormatToDepth(gfxImageFormat aFormat);

  /**
   * Return the transform matrix that maps aFrom to the rectangle defined by
   * aToTopLeft/aToTopRight/aToBottomRight. aFrom must be
   * nonempty and the destination rectangle must be axis-aligned.
   */
  static gfxMatrix TransformRectToRect(const gfxRect& aFrom,
                                       const gfxPoint& aToTopLeft,
                                       const gfxPoint& aToTopRight,
                                       const gfxPoint& aToBottomRight);

  static Matrix TransformRectToRect(const gfxRect& aFrom,
                                    const IntPoint& aToTopLeft,
                                    const IntPoint& aToTopRight,
                                    const IntPoint& aToBottomRight);

  /**
   * If aIn can be represented exactly using an gfx::IntRect (i.e.
   * integer-aligned edges and coordinates in the int32_t range) then we
   * set aOut to that rectangle, otherwise return failure.
   */
  static bool GfxRectToIntRect(const gfxRect& aIn, mozilla::gfx::IntRect* aOut);

  /* Conditions this border to Cairo's max coordinate space.
   * The caller can check IsEmpty() after Condition() -- if it's TRUE,
   * the caller can possibly avoid doing any extra rendering.
   */
  static void ConditionRect(gfxRect& aRect);

  /*
   * Transform this rectangle with aMatrix, resulting in a gfxQuad.
   */
  static gfxQuad TransformToQuad(const gfxRect& aRect,
                                 const mozilla::gfx::Matrix4x4& aMatrix);

  /**
   * Return the smallest power of kScaleResolution (2) greater than or equal to
   * aVal. If aRoundDown is specified, the power of 2 will rather be less than
   * or equal to aVal.
   */
  static float ClampToScaleFactor(float aVal, bool aRoundDown = false);

  /**
   * We can snap layer transforms for two reasons:
   * 1) To avoid unnecessary resampling when a transform is a translation
   * by a non-integer number of pixels.
   * Snapping the translation to an integer number of pixels avoids
   * blurring the layer and can be faster to composite.
   * 2) When a layer is used to render a rectangular object, we need to
   * emulate the rendering of rectangular inactive content and snap the
   * edges of the rectangle to pixel boundaries. This is both to ensure
   * layer rendering is consistent with inactive content rendering, and to
   * avoid seams.
   * This function implements type 1 snapping. If aTransform is a 2D
   * translation, and this layer's layer manager has enabled snapping
   * (which is the default), return aTransform with the translation snapped
   * to nearest pixels. Otherwise just return aTransform. Call this when the
   * layer does not correspond to a single rectangular content object.
   * This function does not try to snap if aTransform has a scale, because in
   * that case resampling is inevitable and there's no point in trying to
   * avoid it. In fact snapping can cause problems because pixel edges in the
   * layer's content can be rendered unpredictably (jiggling) as the scale
   * interacts with the snapping of the translation, especially with animated
   * transforms.
   * @param aResidualTransform a transform to apply before the result transform
   * in order to get the results to completely match aTransform.
   */
  static Matrix4x4 SnapTransformTranslation(const Matrix4x4& aTransform,
                                            Matrix* aResidualTransform);
  static Matrix SnapTransformTranslation(const Matrix& aTransform,
                                         Matrix* aResidualTransform);
  static Matrix4x4 SnapTransformTranslation3D(const Matrix4x4& aTransform,
                                              Matrix* aResidualTransform);
  /**
   * See comment for SnapTransformTranslation.
   * This function implements type 2 snapping. If aTransform is a translation
   * and/or scale, transform aSnapRect by aTransform, snap to pixel boundaries,
   * and return the transform that maps aSnapRect to that rect. Otherwise
   * just return aTransform.
   * @param aSnapRect a rectangle whose edges should be snapped to pixel
   * boundaries in the destination surface.
   * @param aResidualTransform a transform to apply before the result transform
   * in order to get the results to completely match aTransform.
   */
  static Matrix4x4 SnapTransform(const Matrix4x4& aTransform,
                                 const gfxRect& aSnapRect,
                                 Matrix* aResidualTransform);
  static Matrix SnapTransform(const Matrix& aTransform,
                              const gfxRect& aSnapRect,
                              Matrix* aResidualTransform);

  /**
   * Clears surface to aColor (which defaults to transparent black).
   */
  static void ClearThebesSurface(gfxASurface* aSurface);

  static const float* YuvToRgbMatrix4x3RowMajor(
      mozilla::gfx::YUVColorSpace aYUVColorSpace);
  static const float* YuvToRgbMatrix3x3ColumnMajor(
      mozilla::gfx::YUVColorSpace aYUVColorSpace);
  static const float* YuvToRgbMatrix4x4ColumnMajor(
      mozilla::gfx::YUVColorSpace aYUVColorSpace);

  static mozilla::Maybe<mozilla::gfx::YUVColorSpace> CicpToColorSpace(
      const mozilla::gfx::CICP::MatrixCoefficients,
      const mozilla::gfx::CICP::ColourPrimaries,
      mozilla::LazyLogModule& aLogger);

  static mozilla::Maybe<mozilla::gfx::ColorSpace2> CicpToColorPrimaries(
      const mozilla::gfx::CICP::ColourPrimaries,
      mozilla::LazyLogModule& aLogger);

  static mozilla::Maybe<mozilla::gfx::TransferFunction> CicpToTransferFunction(
      const mozilla::gfx::CICP::TransferCharacteristics);

  /**
   * Creates a copy of aSurface, but having the SurfaceFormat aFormat.
   *
   * This function always creates a new surface. Do not call it if aSurface's
   * format is the same as aFormat. Such a non-conversion would just be an
   * unnecessary and wasteful copy (this function asserts to prevent that).
   *
   * This function is intended to be called by code that needs to access the
   * pixel data of the surface, but doesn't want to have lots of branches
   * to handle different pixel data formats (code which would become out of
   * date if and when new formats are added). Callers can use this function
   * to copy the surface to a specified format so that they only have to
   * handle pixel data in that one format.
   *
   * WARNING: There are format conversions that will not be supported by this
   * function. It very much depends on what the Moz2D backends support. If
   * the temporary B8G8R8A8 DrawTarget that this function creates has a
   * backend that supports DrawSurface() calls passing a surface with
   * aSurface's format it will work. Otherwise it will not.
   *
   *                      *** IMPORTANT PERF NOTE ***
   *
   * This function exists partly because format conversion is fraught with
   * non-obvious performance hazards, so we don't want Moz2D consumers to be
   * doing their own format conversion. Do not try to do so, or at least read
   * the comments in this functions implemtation. That said, the copy that
   * this function carries out has a cost and, although this function tries
   * to avoid perf hazards such as expensive uploads to/readbacks from the
   * GPU, it can't guarantee that it always successfully does so. Perf
   * critical code that can directly handle the common formats that it
   * encounters in a way that is cheaper than a copy-with-format-conversion
   * should consider doing so, and only use this function as a fallback to
   * handle other formats.
   *
   * XXXjwatt it would be nice if SourceSurface::GetDataSurface took a
   * SurfaceFormat argument (with a default argument meaning "use the
   * existing surface's format") and returned a DataSourceSurface in that
   * format. (There would then be an issue of callers maybe failing to
   * realize format conversion may involve expensive copying/uploading/
   * readback.)
   */
  static already_AddRefed<DataSourceSurface>
  CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
                                           SurfaceFormat aFormat);

  // Scales a SourceSurface to the new requested size.
  //
  // Asserts when the requested size is equal to the current size of the
  // surface.
  static already_AddRefed<SourceSurface> ScaleSourceSurface(
      SourceSurface& aSurface, const mozilla::gfx::IntSize& aTargetSize);

  /**
   * Return a color that can be used to identify a frame with a given frame
   * number. The colors will cycle after sNumFrameColors.  You can query colors
   * 0 .. sNumFrameColors-1 to get all the colors back.
   */
  static const mozilla::gfx::DeviceColor& GetColorForFrameNumber(
      uint64_t aFrameNumber);
  static const uint32_t sNumFrameColors;

  enum BinaryOrData { eBinaryEncode, eDataURIEncode };

  /**
   * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder.
   * If both aFile and aString are null, the encoded data is copied to the
   * clipboard.
   *
   * @param aImageType The image type that the surface is to be encoded to.
   *   Used to create an appropriate imgIEncoder instance to do the encoding.
   *
   * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as
   *   the value of the |outputOptions| parameter. Callers are responsible
   *   for making sure that this is a reasonable value for the passed MIME-type
   *   (i.e. for the type of encoder that will be created).
   *
   * @aBinaryOrData Flag used to determine if the surface is simply encoded
   *   to the requested binary image format, or if the binary image is
   *   further converted to base-64 and written out as a 'data:' URI.
   *
   * @aFile If specified, the encoded data is written out to aFile.
   *
   * @aString If specified, the encoded data is written out to aString.
   *
   * TODO: Copying to the clipboard as a binary file is not currently
   * supported.
   */
  static nsresult EncodeSourceSurface(SourceSurface* aSurface,
                                      const ImageType aImageType,
                                      const nsAString& aOutputOptions,
                                      BinaryOrData aBinaryOrData, FILE* aFile,
                                      nsACString* aString = nullptr);

  /**
   * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder
   * and returns the result as an nsIInputStream.
   *
   * @param aSurface The source surface to encode
   *
   * @param aImageType The image type that the surface is to be encoded to.
   *   Used to create an appropriate imgIEncoder instance to do the encoding.
   *
   * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as
   *   the value of the |outputOptions| parameter. Callers are responsible
   *   for making sure that this is a reasonable value for the passed MIME-type
   *   (i.e. for the type of encoder that will be created).
   *
   * @param aOutStream pointer to the output stream
   *
   */
  static nsresult EncodeSourceSurfaceAsStream(SourceSurface* aSurface,
                                              const ImageType aImageType,
                                              const nsAString& aOutputOptions,
                                              nsIInputStream** aOutStream);

  /**
   * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder
   * and returns the result as a vector of bytes
   *
   * @param aImageType The image type that the surface is to be encoded to.
   *   Used to create an appropriate imgIEncoder instance to do the encoding.
   *
   * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as
   *   the value of the |outputOptions| parameter. Callers are responsible
   *   for making sure that this is a reasonable value for the passed MIME-type
   *   (i.e. for the type of encoder that will be created).
   *
   */
  static mozilla::Maybe<nsTArray<uint8_t>> EncodeSourceSurfaceAsBytes(
      SourceSurface* aSurface, const ImageType aImageType,
      const nsAString& aOutputOptions);

  /**
   * Write as a PNG file to the path aFile.
   */
  static void WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile);
  static void WriteAsPNG(SourceSurface* aSurface, const char* aFile);
  static void WriteAsPNG(DrawTarget* aDT, const nsAString& aFile);
  static void WriteAsPNG(DrawTarget* aDT, const char* aFile);

  /**
   * Dump as a PNG encoded Data URL to a FILE stream (using stdout by
   * default).
   *
   * Rather than giving aFile a default argument we have separate functions
   * to make them easier to use from a debugger.
   */
  static void DumpAsDataURI(SourceSurface* aSourceSurface, FILE* aFile);
  static inline void DumpAsDataURI(SourceSurface* aSourceSurface) {
    DumpAsDataURI(aSourceSurface, stdout);
  }
  static void DumpAsDataURI(DrawTarget* aDT, FILE* aFile);
  static inline void DumpAsDataURI(DrawTarget* aDT) {
    DumpAsDataURI(aDT, stdout);
  }
  static nsCString GetAsDataURI(SourceSurface* aSourceSurface);
  static nsCString GetAsDataURI(DrawTarget* aDT);
  static nsCString GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface);

  static mozilla::UniquePtr<uint8_t[]> GetImageBuffer(
      DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
      int32_t* outFormat);

  static mozilla::UniquePtr<uint8_t[]> GetImageBufferWithRandomNoise(
      DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
      nsICookieJarSettings* aCookieJarSettings, nsIPrincipal* aPrincipal,
      int32_t* outFormat);

  static nsresult GetInputStream(DataSourceSurface* aSurface,
                                 bool aIsAlphaPremultiplied,
                                 const char* aMimeType,
                                 const nsAString& aEncoderOptions,
                                 nsIInputStream** outStream);

  static nsresult GetInputStreamWithRandomNoise(
      DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
      const char* aMimeType, const nsAString& aEncoderOptions,
      nsICookieJarSettings* aCookieJarSettings, nsIPrincipal* aPrincipal,
      nsIInputStream** outStream);

  static void RemoveShaderCacheFromDiskIfNecessary();

  /**
   * Copy to the clipboard as a PNG encoded Data URL.
   */
  static void CopyAsDataURI(SourceSurface* aSourceSurface);
  static void CopyAsDataURI(DrawTarget* aDT);

  static bool DumpDisplayList();

  static FILE* sDumpPaintFile;
};

namespace mozilla {

// Container for either a single element of type T, or an nsTArray<T>.
// Provides a minimal subset of nsTArray's API, just enough to support use
// by ContextState for the clipsAndTransforms list, and by gfxTextRun for
// its mGlyphRuns.
// Using this instead of a simple nsTArray avoids an extra allocation in the
// common case where no more than one element is ever added to the list.
// Unlike an AutoTArray<..., 1>, this class is memmovable and therefore can
// be used in ContextState without breaking its movability.
template <typename T>
class ElementOrArray {
  union {
    T mElement;
    nsTArray<T> mArray;
  };
  enum class Tag : uint8_t {
    Element,
    Array,
  } mTag;

  // gfxTextRun::SortGlyphRuns and SanitizeGlyphRuns directly access the array.
  friend class ::gfxTextRun;
  nsTArray<T>& Array() {
    MOZ_DIAGNOSTIC_ASSERT(mTag == Tag::Array);
    return mArray;
  }

 public:
  // Construct as an empty array.
  ElementOrArray() : mTag(Tag::Array) { new (&mArray) nsTArray<T>(); }

  // For now, don't support copy/move.
  ElementOrArray(const ElementOrArray&) = delete;
  ElementOrArray(ElementOrArray&&) = delete;

  ElementOrArray& operator=(const ElementOrArray&) = delete;
  ElementOrArray& operator=(ElementOrArray&&) = delete;

  // Destroy the appropriate variant.
  ~ElementOrArray() {
    switch (mTag) {
      case Tag::Element:
        mElement.~T();
        break;
      case Tag::Array:
        mArray.~nsTArray();
        break;
    }
  }

  size_t Length() const { return mTag == Tag::Element ? 1 : mArray.Length(); }

  T* AppendElement(const T& aElement) {
    switch (mTag) {
      case Tag::Element: {
        // Move the existing element into an array, then append the new one.
        T temp = std::move(mElement);
        mElement.~T();
        mTag = Tag::Array;
        new (&mArray) nsTArray<T>();
        mArray.AppendElement(std::move(temp));
        return mArray.AppendElement(aElement);
      }
      case Tag::Array: {
        // If currently empty, just store the element directly.
        if (mArray.IsEmpty()) {
          mArray.~nsTArray();
          mTag = Tag::Element;
          new (&mElement) T(aElement);
          return &mElement;
        }
        // Otherwise, append it to the array.
        return mArray.AppendElement(aElement);
      }
      default:
        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("invalid tag");
    }
  }

  const T& LastElement() const {
    return mTag == Tag::Element ? mElement : mArray.LastElement();
  }

  T& LastElement() {
    return mTag == Tag::Element ? mElement : mArray.LastElement();
  }

  bool IsEmpty() const {
    return mTag == Tag::Element ? false : mArray.IsEmpty();
  }

  void TruncateLength(uint32_t aLength = 0) {
    MOZ_DIAGNOSTIC_ASSERT(aLength <= Length());
    switch (mTag) {
      case Tag::Element:
        if (aLength == 0) {
          // Destroy the single element, and convert to an empty array.
          mElement.~T();
          mTag = Tag::Array;
          new (&mArray) nsTArray<T>();
        }
        break;
      case Tag::Array:
        mArray.TruncateLength(aLength);
        break;
    }
  }

  void Clear() {
    switch (mTag) {
      case Tag::Element:
        mElement.~T();
        mTag = Tag::Array;
        new (&mArray) nsTArray<T>();
        break;
      case Tag::Array:
        mArray.Clear();
        break;
    }
  }

  // Convert from Array to Element storage. Only to be used when the current
  // state is a single-element array!
  void ConvertToElement() {
    MOZ_DIAGNOSTIC_ASSERT(mTag == Tag::Array && mArray.Length() == 1);
    T temp = std::move(mArray[0]);
    mArray.~nsTArray();
    mTag = Tag::Element;
    new (&mElement) T(std::move(temp));
  }

  const T& operator[](uint32_t aIndex) const {
    MOZ_DIAGNOSTIC_ASSERT(aIndex < Length());
    return mTag == Tag::Element ? mElement : mArray[aIndex];
  }
  T& operator[](uint32_t aIndex) {
    MOZ_DIAGNOSTIC_ASSERT(aIndex < Length());
    return mTag == Tag::Element ? mElement : mArray[aIndex];
  }

  // Simple iterators to support range-for loops.
  const T* begin() const {
    return mTag == Tag::Array ? mArray.IsEmpty() ? nullptr : &*mArray.begin()
                              : &mElement;
  }
  T* begin() {
    return mTag == Tag::Array ? mArray.IsEmpty() ? nullptr : &*mArray.begin()
                              : &mElement;
  }

  const T* end() const {
    return mTag == Tag::Array ? begin() + mArray.Length() : &mElement + 1;
  }
  T* end() {
    return mTag == Tag::Array ? begin() + mArray.Length() : &mElement + 1;
  }

  size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
    return mTag == Tag::Array ? mArray.ShallowSizeOfExcludingThis(aMallocSizeOf)
                              : 0;
  }
};

struct StyleAbsoluteColor;

namespace gfx {

/**
 * If the CMS mode is CMSMode::All, these functions transform the passed
 * color to a device color using the transform returned by
 * gfxPlatform::GetCMSRGBTransform().  If the CMS mode is some other value, the
 * color is returned unchanged (other than a type change to Moz2D Color, if
 * applicable).
 */
DeviceColor ToDeviceColor(const sRGBColor&);
DeviceColor ToDeviceColor(const StyleAbsoluteColor&);
DeviceColor ToDeviceColor(nscolor);

sRGBColor ToSRGBColor(const StyleAbsoluteColor&);

/**
 * Performs a checked multiply of the given width, height, and bytes-per-pixel
 * values.
 */
static inline CheckedInt<uint32_t> SafeBytesForBitmap(uint32_t aWidth,
                                                      uint32_t aHeight,
                                                      unsigned aBytesPerPixel) {
  MOZ_ASSERT(aBytesPerPixel > 0);
  CheckedInt<uint32_t> width = uint32_t(aWidth);
  CheckedInt<uint32_t> height = uint32_t(aHeight);
  return width * height * aBytesPerPixel;
}

}  // namespace gfx
}  // namespace mozilla

#endif
