/* -*- 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/. */

#ifndef nsImageRenderer_h__
#define nsImageRenderer_h__

#include "nsStyleStruct.h"
#include "Units.h"
#include "mozilla/AspectRatio.h"
#include "mozilla/SurfaceFromElementResult.h"

class gfxDrawable;

namespace mozilla {
class nsDisplayItem;

namespace layers {
class StackingContextHelper;
class WebRenderParentCommand;
class RenderRootStateManager;
}  // namespace layers

namespace wr {
class DisplayListBuilder;
class IpcResourceUpdateQueue;
}  // namespace wr

// A CSSSizeOrRatio represents a (possibly partially specified) size for use
// in computing image sizes. Either or both of the width and height might be
// given. A ratio of width to height may also be given. If we at least two
// of these then we can compute a concrete size, that is a width and height.
struct CSSSizeOrRatio {
  CSSSizeOrRatio()
      : mWidth(0), mHeight(0), mHasWidth(false), mHasHeight(false) {}

  bool CanComputeConcreteSize() const {
    return mHasWidth + mHasHeight + HasRatio() >= 2;
  }
  bool IsConcrete() const { return mHasWidth && mHasHeight; }
  bool HasRatio() const { return !!mRatio; }
  bool IsEmpty() const {
    return (mHasWidth && mWidth <= 0) || (mHasHeight && mHeight <= 0) ||
           !mRatio;
  }

  // CanComputeConcreteSize must return true when ComputeConcreteSize is
  // called.
  nsSize ComputeConcreteSize() const;

  void SetWidth(nscoord aWidth) {
    mWidth = aWidth;
    mHasWidth = true;
    if (mHasHeight) {
      mRatio = AspectRatio::FromSize(mWidth, mHeight);
    }
  }
  void SetHeight(nscoord aHeight) {
    mHeight = aHeight;
    mHasHeight = true;
    if (mHasWidth) {
      mRatio = AspectRatio::FromSize(mWidth, mHeight);
    }
  }
  void SetSize(const nsSize& aSize) {
    mWidth = aSize.width;
    mHeight = aSize.height;
    mHasWidth = true;
    mHasHeight = true;
    mRatio = AspectRatio::FromSize(mWidth, mHeight);
  }
  void SetRatio(const AspectRatio& aRatio) {
    MOZ_ASSERT(
        !mHasWidth || !mHasHeight,
        "Probably shouldn't be setting a ratio if we have a concrete size");
    mRatio = aRatio;
  }

  AspectRatio mRatio;
  nscoord mWidth;
  nscoord mHeight;
  bool mHasWidth;
  bool mHasHeight;
};

/**
 * This is a small wrapper class to encapsulate image drawing that can draw an
 * StyleImage image, which may internally be a real image, a sub image, or a CSS
 * gradient, etc...
 *
 * @note Always call the member functions in the order of PrepareImage(),
 * SetSize(), and Draw*().
 */
class nsImageRenderer {
 public:
  typedef mozilla::image::ImgDrawResult ImgDrawResult;
  typedef mozilla::layers::ImageContainer ImageContainer;

  enum {
    FLAG_SYNC_DECODE_IMAGES = 0x01,
    FLAG_PAINTING_TO_WINDOW = 0x02,
    FLAG_HIGH_QUALITY_SCALING = 0x04,
    FLAG_DRAW_PARTIAL_FRAMES = 0x08
  };
  enum FitType { CONTAIN, COVER };

  nsImageRenderer(nsIFrame* aForFrame, const mozilla::StyleImage* aImage,
                  uint32_t aFlags);
  ~nsImageRenderer() = default;
  /**
   * Populates member variables to get ready for rendering.
   * @return true iff the image is ready, and there is at least a pixel to
   * draw.
   */
  bool PrepareImage();

  /**
   * The three Compute*Size functions correspond to the sizing algorthms and
   * definitions from the CSS Image Values and Replaced Content spec. See
   * http://dev.w3.org/csswg/css-images-3/#sizing .
   */

  /**
   * Compute the intrinsic size of the image as defined in the CSS Image Values
   * spec. The intrinsic size is the unscaled size which the image would ideally
   * like to be in app units.
   */
  mozilla::CSSSizeOrRatio ComputeIntrinsicSize();

  /**
   * Computes the placement for a background image, or for the image data
   * inside of a replaced element.
   *
   * @param aPos The CSS <position> value that specifies the image's position.
   * @param aOriginBounds The box to which the tiling position should be
   *          relative. For background images, this should correspond to
   *          'background-origin' for the frame, except when painting on the
   *          canvas, in which case the origin bounds should be the bounds
   *          of the root element's frame. For a replaced element, this should
   *          be the element's content-box.
   * @param aTopLeft [out] The top-left corner where an image tile should be
   *          drawn.
   * @param aAnchorPoint [out] A point which should be pixel-aligned by
   *          nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless
   *          CSS specifies a percentage (including 'right' or 'bottom'), in
   *          which case it's that percentage within of aOriginBounds. So
   *          'right' would set aAnchorPoint.x to aOriginBounds.XMost().
   *
   * Points are returned relative to aOriginBounds.
   */
  static void ComputeObjectAnchorPoint(const mozilla::Position& aPos,
                                       const nsSize& aOriginBounds,
                                       const nsSize& aImageSize,
                                       nsPoint* aTopLeft,
                                       nsPoint* aAnchorPoint);

  /**
   * Compute the size of the rendered image using either the 'cover' or
   * 'contain' constraints (aFitType).
   */
  static nsSize ComputeConstrainedSize(
      const nsSize& aConstrainingSize,
      const mozilla::AspectRatio& aIntrinsicRatio, FitType aFitType);
  /**
   * Compute the size of the rendered image (the concrete size) where no cover/
   * contain constraints are given. The 'default algorithm' from the CSS Image
   * Values spec.
   */
  static nsSize ComputeConcreteSize(
      const mozilla::CSSSizeOrRatio& aSpecifiedSize,
      const mozilla::CSSSizeOrRatio& aIntrinsicSize,
      const nsSize& aDefaultSize);

  /**
   * Set this image's preferred size. This will be its intrinsic size where
   * specified and the default size where it is not. Used as the unscaled size
   * when rendering the image.
   */
  void SetPreferredSize(const mozilla::CSSSizeOrRatio& aIntrinsicSize,
                        const nsSize& aDefaultSize);

  /**
   * Draws the image to the target rendering context using
   * {background|mask}-specific arguments.
   * @see nsLayoutUtils::DrawImage() for parameters.
   */
  ImgDrawResult DrawLayer(nsPresContext* aPresContext,
                          gfxContext& aRenderingContext, const nsRect& aDest,
                          const nsRect& aFill, const nsPoint& aAnchor,
                          const nsRect& aDirty, const nsSize& aRepeatSize,
                          float aOpacity);

  /**
   * Builds WebRender DisplayItems for an image using
   * {background|mask}-specific arguments.
   * @see nsLayoutUtils::DrawImage() for parameters.
   */
  ImgDrawResult BuildWebRenderDisplayItemsForLayer(
      nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder,
      mozilla::wr::IpcResourceUpdateQueue& aResource,
      const mozilla::layers::StackingContextHelper& aSc,
      mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
      const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor,
      const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity);

  /**
   * Draw the image to a single component of a border-image style rendering.
   * aFill The destination rect to be drawn into
   * aSrc is the part of the image to be rendered into a tile (aUnitSize in
   * aFill), if aSrc and the dest tile are different sizes, the image will be
   * scaled to map aSrc onto the dest tile.
   * aHFill and aVFill are the repeat patterns for the component -
   * NS_STYLE_BORDER_IMAGE_REPEAT_*
   * aUnitSize The scaled size of a single source rect (in destination coords)
   * aIndex identifies the component: 0 1 2
   *                                  3 4 5
   *                                  6 7 8
   * aSVGViewportSize The image size evaluated by default sizing algorithm.
   * Pass Nothing() if we can read a valid viewport size or aspect-ratio from
   * the drawing image directly, otherwise, pass Some() with viewport size
   * evaluated from default sizing algorithm.
   * aHasIntrinsicRatio is used to record if the source image has fixed
   * intrinsic ratio.
   */
  ImgDrawResult DrawBorderImageComponent(
      nsPresContext* aPresContext, gfxContext& aRenderingContext,
      const nsRect& aDirtyRect, const nsRect& aFill,
      const mozilla::CSSIntRect& aSrc, mozilla::StyleBorderImageRepeat aHFill,
      mozilla::StyleBorderImageRepeat aVFill, const nsSize& aUnitSize,
      uint8_t aIndex, const mozilla::Maybe<nsSize>& aSVGViewportSize,
      const bool aHasIntrinsicRatio);

  /**
   * Draw the image to aRenderingContext which can be used to define the
   * float area in the presence of "shape-outside: <image>".
   */
  ImgDrawResult DrawShapeImage(nsPresContext* aPresContext,
                               gfxContext& aRenderingContext);

  bool IsRasterImage();

  /// Retrieves the image associated with this nsImageRenderer, if there is one.
  already_AddRefed<imgIContainer> GetImage();

  bool IsReady() const { return mPrepareResult == ImgDrawResult::SUCCESS; }
  ImgDrawResult PrepareResult() const { return mPrepareResult; }
  void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; }
  void SetMaskOp(mozilla::StyleMaskMode aMaskOp) { mMaskOp = aMaskOp; }
  const nsSize& GetSize() const { return mSize; }
  mozilla::StyleImage::Tag GetType() const { return mType; }
  const mozilla::StyleGradient* GetGradientData() const {
    return mGradientData;
  }

 private:
  /**
   * Draws the image to the target rendering context.
   * aSrc is a rect on the source image which will be mapped to aDest; it's
   * currently only used for gradients.
   *
   * @see nsLayoutUtils::DrawImage() for other parameters.
   */
  ImgDrawResult Draw(nsPresContext* aPresContext, gfxContext& aRenderingContext,
                     const nsRect& aDirtyRect, const nsRect& aDest,
                     const nsRect& aFill, const nsPoint& aAnchor,
                     const nsSize& aRepeatSize, const mozilla::CSSIntRect& aSrc,
                     float aOpacity = 1.0);

  /**
   * Builds WebRender DisplayItems for the image.
   * aSrc is a rect on the source image which will be mapped to aDest; it's
   * currently only used for gradients.
   *
   * @see nsLayoutUtils::DrawImage() for other parameters.
   */
  ImgDrawResult BuildWebRenderDisplayItems(
      nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder,
      mozilla::wr::IpcResourceUpdateQueue& aResources,
      const mozilla::layers::StackingContextHelper& aSc,
      mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
      const nsRect& aDirtyRect, const nsRect& aDest, const nsRect& aFill,
      const nsPoint& aAnchor, const nsSize& aRepeatSize,
      const mozilla::CSSIntRect& aSrc, float aOpacity = 1.0);

  /**
   * Helper method for creating a gfxDrawable from mPaintServerFrame or
   * mImageElementSurface.
   * Requires mType to be Element.
   * Returns null if we cannot create the drawable.
   */
  already_AddRefed<gfxDrawable> DrawableForElement(const nsRect& aImageRect,
                                                   gfxContext& aContext);

  nsIFrame* mForFrame;
  const mozilla::StyleImage* mImage;
  ImageResolution mImageResolution;
  mozilla::StyleImage::Tag mType;
  nsCOMPtr<imgIContainer> mImageContainer;
  const mozilla::StyleGradient* mGradientData;
  nsIFrame* mPaintServerFrame;
  SurfaceFromElementResult mImageElementSurface;
  ImgDrawResult mPrepareResult;
  nsSize mSize;  // unscaled size of the image, in app units
  uint32_t mFlags;
  mozilla::gfx::ExtendMode mExtendMode;
  mozilla::StyleMaskMode mMaskOp;
};

}  // namespace mozilla

#endif /* nsImageRenderer_h__ */
