/* -*- Mode: C++; tab-width: 2; 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 __NS_SVGFILTERINSTANCE_H__
#define __NS_SVGFILTERINSTANCE_H__

#include "gfxMatrix.h"
#include "gfxPoint.h"
#include "gfxRect.h"
#include "nsCOMPtr.h"
#include "nsHashKeys.h"
#include "nsPoint.h"
#include "nsRect.h"
#include "nsSize.h"
#include "nsSVGFilters.h"
#include "nsSVGNumber2.h"
#include "nsSVGNumberPair.h"
#include "nsTArray.h"

class gfxASurface;
class gfxImageSurface;
class nsIFrame;
class nsSVGFilterPaintCallback;

namespace mozilla {
namespace dom {
class SVGFilterElement;
}
}

/**
 * This class performs all filter processing.
 * 
 * We build a graph of the filter image data flow, essentially
 * converting the filter graph to SSA. This lets us easily propagate
 * analysis data (such as bounding-boxes) over the filter primitive graph.
 *
 * Definition of "filter space": filter space is a coordinate system that is
 * aligned with the user space of the filtered element, with its origin located
 * at the top left of the filter region (as specified by our ctor's
 * aFilterRegion, and returned by our GetFilterRegion, specifically), and with
 * one unit equal in size to one pixel of the offscreen surface into which the
 * filter output would/will be painted.
 *
 * The definition of "filter region" can be found here:
 * http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion
 */
class nsSVGFilterInstance
{
public:
  /**
   * @param aTargetFrame The frame of the filtered element under consideration.
   * @param aPaintCallback [optional] The callback that Render() should use to
   *   paint. Only required if you will call Render().
   * @param aFilterElement The filter element referenced by aTargetFrame's
   *   element.
   * @param aTargetBBox The filtered element's bbox, in the filtered element's
   *   user space.
   * @param aFilterRegion The "filter region", in the filtered element's user
   *   space. The caller must have already expanded the region out so that its
   *   edges coincide with pixel boundaries in the offscreen surface that
   *   would/will be created to paint the filter output.
   * @param aFilterSpaceSize The size of the user specified "filter region",
   *   in filter space units.
   * @param aFilterSpaceToDeviceSpaceTransform The transform from filter
   *   space to outer-<svg> device space.
   * @param aTargetBounds The pre-filter paint bounds of the filtered element,
   *   in filter space.
   * @param aPostFilterDirtyRect [optional] The bounds of the post-filter area
   *   that has to be repainted, in filter space. Only required if you will
   *   call ComputeSourceNeededRect() or Render().
   * @param aPreFilterDirtyRect [optional] The bounds of the pre-filter area of
   *   the filtered element that changed, in filter space. Only required if you
   *   will call ComputePostFilterDirtyRect().
   * @param aPrimitiveUnits The value from the 'primitiveUnits' attribute.
   */
  nsSVGFilterInstance(nsIFrame *aTargetFrame,
                      nsSVGFilterPaintCallback *aPaintCallback,
                      const mozilla::dom::SVGFilterElement *aFilterElement,
                      const gfxRect &aTargetBBox,
                      const gfxRect& aFilterRegion,
                      const nsIntSize& aFilterSpaceSize,
                      const gfxMatrix &aFilterSpaceToDeviceSpaceTransform,
                      const gfxMatrix &aFilterSpaceToFrameSpaceInCSSPxTransform,
                      const nsIntRect& aTargetBounds,
                      const nsIntRect& aPostFilterDirtyRect,
                      const nsIntRect& aPreFilterDirtyRect,
                      uint16_t aPrimitiveUnits,
                      nsIFrame* aTransformRoot) :
    mTargetFrame(aTargetFrame),
    mPaintCallback(aPaintCallback),
    mFilterElement(aFilterElement),
    mTargetBBox(aTargetBBox),
    mFilterSpaceToDeviceSpaceTransform(aFilterSpaceToDeviceSpaceTransform),
    mFilterSpaceToFrameSpaceInCSSPxTransform(aFilterSpaceToFrameSpaceInCSSPxTransform),
    mFilterRegion(aFilterRegion),
    mFilterSpaceSize(aFilterSpaceSize),
    mSurfaceRect(nsIntPoint(0, 0), aFilterSpaceSize),
    mTargetBounds(aTargetBounds),
    mPostFilterDirtyRect(aPostFilterDirtyRect),
    mPreFilterDirtyRect(aPreFilterDirtyRect),
    mPrimitiveUnits(aPrimitiveUnits),
    mTransformRoot(aTransformRoot) {
  }

  /**
   * Returns the user specified "filter region", in the filtered element's user
   * space, after it has been adjusted out (if necessary) so that its edges
   * coincide with pixel boundaries of the offscreen surface into which the
   * filtered output would/will be painted.
   */
  gfxRect GetFilterRegion() const { return mFilterRegion; }

  /**
   * Returns the size of the user specified "filter region", in filter space.
   * The size will be {filterRes.x by filterRes.y}, whether the user specified
   * the filter's filterRes attribute explicitly, or the implementation chose
   * the filterRes values. (The top-left of the filter region is the origin of
   * filter space, which is why this method returns an nsIntSize and not an
   * nsIntRect.)
   */
  const nsIntSize& GetFilterSpaceSize() { return mFilterSpaceSize; }
  uint32_t GetFilterResX() const { return mFilterSpaceSize.width; }
  uint32_t GetFilterResY() const { return mFilterSpaceSize.height; }

  /**
   * Returns the dimensions of the offscreen surface that is required for the
   * output from the current filter operation, in filter space. This rect is
   * clipped to, and therefore guaranteed to be fully contained by, the filter
   * region.
   */
  const nsIntRect& GetSurfaceRect() const { return mSurfaceRect; }
  int32_t GetSurfaceWidth() const { return mSurfaceRect.width; }
  int32_t GetSurfaceHeight() const { return mSurfaceRect.height; }

  /**
   * Allocates a gfxASurface, renders the filtered element into the surface,
   * and then returns the surface via the aOutput outparam. The area that
   * needs to be painted must have been specified before calling this method
   * by passing it as the aPostFilterDirtyRect argument to the
   * nsSVGFilterInstance constructor.
   */
  nsresult Render(gfxASurface** aOutput);

  /**
   * Sets the aPostFilterDirtyRect outparam to the post-filter bounds in filter
   * space of the area that would be dirtied by mTargetFrame when a given
   * pre-filter area of mTargetFrame is dirtied. The pre-filter area must have
   * been specified before calling this method by passing it as the
   * aPreFilterDirtyRect argument to the nsSVGFilterInstance constructor.
   */
  nsresult ComputePostFilterDirtyRect(nsIntRect* aPostFilterDirtyRect);

  /**
   * Sets the aDirty outparam to the pre-filter bounds in filter space of the
   * area of mTargetFrame that is needed in order to paint the filtered output
   * for a given post-filter dirtied area. The post-filter area must have been
   * specified before calling this method by passing it as the aPostFilterDirtyRect
   * argument to the nsSVGFilterInstance constructor.
   */
  nsresult ComputeSourceNeededRect(nsIntRect* aDirty);

  /**
   * Sets the aDirty outparam to the post-filter bounds in filter space of the
   * area that would be dirtied by mTargetFrame if its entire pre-filter area
   * is dirtied.
   */
  nsresult ComputeOutputBBox(nsIntRect* aBBox);

  float GetPrimitiveNumber(uint8_t aCtxType, const nsSVGNumber2 *aNumber) const
  {
    return GetPrimitiveNumber(aCtxType, aNumber->GetAnimValue());
  }
  float GetPrimitiveNumber(uint8_t aCtxType, const nsSVGNumberPair *aNumberPair,
                           nsSVGNumberPair::PairIndex aIndex) const
  {
    return GetPrimitiveNumber(aCtxType, aNumberPair->GetAnimValue(aIndex));
  }

  /**
   * Converts a userSpaceOnUse/objectBoundingBoxUnits unitless point and length
   * into filter space, depending on the value of mPrimitiveUnits. (For
   * objectBoundingBoxUnits, the bounding box offset is applied to the point.)
   */
  void ConvertLocation(float aValues[3]) const;

  /**
   * Returns the transform from the filtered element's user space to filter
   * space. This will be a simple translation and/or scale.
   */
  gfxMatrix GetUserSpaceToFilterSpaceTransform() const;

  /**
   * Returns the transform from filter space to outer-<svg> device space.
   */
  gfxMatrix GetFilterSpaceToDeviceSpaceTransform() const {
    return mFilterSpaceToDeviceSpaceTransform;
  }

  gfxPoint FilterSpaceToUserSpace(const gfxPoint& aPt) const;

  /**
   * Returns the transform from filter space to frame space, in CSS px. This
   * transform does not transform to frame space in its normal app units, since
   * app units are ints, requiring appropriate rounding which can't be done by
   * a transform matrix. Callers have to do that themselves as appropriate for
   * their needs.
   */
  gfxMatrix GetFilterSpaceToFrameSpaceInCSSPxTransform() const {
    return mFilterSpaceToFrameSpaceInCSSPxTransform;
  }

  int32_t AppUnitsPerCSSPixel() const {
    return mTargetFrame->PresContext()->AppUnitsPerCSSPixel();
  }

private:
  typedef nsSVGFE::Image Image;
  typedef nsSVGFE::ColorModel ColorModel;

  struct PrimitiveInfo {
    /// Pointer to the filter primitive element.
    nsSVGFE*  mFE;

    /**
     * The filter space bounds of this filter primitive's output, were a full
     * repaint of mTargetFrame to occur. Note that a filter primitive's output
     * (and hence this rect) is always clipped to both the filter region and
     * to the filter primitive subregion.
     * XXX maybe rename this to mMaxBounds?
     */
    nsIntRect mResultBoundingBox;

    /**
     * The filter space bounds of this filter primitive's output, were we to
     * repaint a given post-filter dirty area, and were we to minimize
     * repainting for that dirty area. In other words this is the part of the
     * primitive's output that is needed by other primitives or the final
     * filtered output in order to repaint that area. This rect is guaranteed
     * to be contained within mResultBoundingBox and, if we're only painting
     * part of the filtered output, may be smaller. This rect is used when
     * calling Render() or ComputeSourceNeededRect().
     * XXX maybe rename this to just mNeededBounds?
     */
    nsIntRect mResultNeededBox;

    /**
     * The filter space bounds of this filter primitive's output, were only
     * part of mTargetFrame's pre-filter output to be dirtied, and were we to
     * minimize repainting for that dirty area. This is used when calculating
     * the area that needs to be invalidated when only part of a filtered
     * element is dirtied. This rect is guaranteed to be contained within
     * mResultBoundingBox.
     */
    nsIntRect mResultChangeBox;

    Image     mImage;

    /**
     * The number of times that this filter primitive's output is used as an
     * input by other filter primitives in the filter graph.
     * XXX seems like we could better use this to avoid creating images for
     * primitives that are not used, or whose ouput in not used during the
     * current operation.
     */
    int32_t   mImageUsers;
  
    // Can't use nsAutoTArray here, because these Info objects
    // live in nsTArrays themselves and nsTArray moves the elements
    // around in memory, which breaks nsAutoTArray.
    nsTArray<PrimitiveInfo*> mInputs;

    PrimitiveInfo() : mFE(nullptr), mImageUsers(0) {}
  };

  class ImageAnalysisEntry : public nsStringHashKey {
  public:
    ImageAnalysisEntry(KeyTypePointer aStr) : nsStringHashKey(aStr) { }
    ImageAnalysisEntry(const ImageAnalysisEntry& toCopy) : nsStringHashKey(toCopy),
      mInfo(toCopy.mInfo) { }

    PrimitiveInfo* mInfo;
  };

  /**
   * Initializes the keyword nodes e.g. SourceGraphic (i.e. sets
   * .mImage.mFilterPrimitiveSubregion and .mResultBoundingBox on
   * mSourceColorAlpha and mSourceAlpha).
   */
  nsresult BuildSources();

  /**
   * Creates a gfxImageSurface for either the FillPaint or StrokePaint graph
   * nodes
   */
  nsresult BuildSourcePaint(PrimitiveInfo *aPrimitive);

  /**
   * Creates a gfxImageSurface for either the FillPaint and StrokePaint graph
   * nodes, fills its contents and assigns it to mFillPaint.mImage.mImage and
   * mStrokePaint.mImage.mImage respectively.
   */
  nsresult BuildSourcePaints();

  /**
   * Creates the gfxImageSurfaces for the SourceGraphic and SourceAlpha graph
   * nodes, paints their contents, and assigns them to
   * mSourceColorAlpha.mImage.mImage and mSourceAlpha.mImage.mImage
   * respectively.
   */
  nsresult BuildSourceImages();

  /**
   * Build the graph of PrimitiveInfo nodes that describes the filter's filter
   * primitives and their connections. This populates mPrimitives, and sets
   * each PrimitiveInfo's mFE, mInputs, mImageUsers, mFilterPrimitiveSubregion,
   * etc.
   */
  nsresult BuildPrimitives();

  /**
   * Compute the filter space bounds of the output from each primitive, were we
   * to do a full repaint of mTargetFrame. This sets mResultBoundingBox on the
   * items in the filter graph, based on the mResultBoundingBox of each item's
   * inputs, and clipped to the filter region and each primitive's filter
   * primitive subregion.
   */
  void ComputeResultBoundingBoxes();

  /**
   * Computes the filter space bounds of the areas that we actually *need* from
   * each filter primitive's output, based on the value of mPostFilterDirtyRect.
   * This sets mResultNeededBox on the items in the filter graph.
   */
   void ComputeNeededBoxes();

  /**
   * Computes the filter space bounds of the area of each filter primitive
   * that will change, based on the value of mPreFilterDirtyRect.
   * This sets mResultChangeBox on the items in the filter graph.
   */
  void ComputeResultChangeBoxes();

  /**
   * Computes and returns the union of all mResultNeededBox rects in the filter
   * graph. This is useful for deciding the size of the offscreen surface that
   * needs to be created for the filter operation.
   */
  nsIntRect ComputeUnionOfAllNeededBoxes();

  /**
   * Allocates and returns a surface of mSurfaceRect.Size(), and with a device
   * offset of -mSurfaceRect.TopLeft(). The surface is cleared to transparent
   * black.
   */
  already_AddRefed<gfxImageSurface> CreateImage();

  /**
   * Computes and sets mFilterPrimitiveSubregion for the given primitive.
   */
  void ComputeFilterPrimitiveSubregion(PrimitiveInfo* aInfo);

  /**
   * If the color model of the pixel data in the aPrimitive's image isn't
   * already aColorModel, then this method converts its pixel data to that
   * color model.
   */
  void EnsureColorModel(PrimitiveInfo* aPrimitive,
                        ColorModel aColorModel);

  /**
   * Scales a numeric filter primitive length in the X, Y or "XY" directions
   * into a length in filter space (no offset is applied).
   */
  float GetPrimitiveNumber(uint8_t aCtxType, float aValue) const;

  gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpace) const;

  /**
   * Clip the filter space rect aRect to the filter region.
   */
  void ClipToFilterSpace(nsIntRect* aRect) const
  {
    nsIntRect filterSpace(nsIntPoint(0, 0), mFilterSpaceSize);
    aRect->IntersectRect(*aRect, filterSpace);
  }

  /**
   * The frame for the element that is currently being filtered.
   */
  nsIFrame*               mTargetFrame;

  nsSVGFilterPaintCallback* mPaintCallback;
  const mozilla::dom::SVGFilterElement* mFilterElement;

  /**
   * The SVG bbox of the element that is being filtered, in user space.
   */
  gfxRect                 mTargetBBox;

  gfxMatrix               mFilterSpaceToDeviceSpaceTransform;
  gfxMatrix               mFilterSpaceToFrameSpaceInCSSPxTransform;
  gfxRect                 mFilterRegion;
  nsIntSize               mFilterSpaceSize;
  nsIntRect               mSurfaceRect;

  /**
   * Pre-filter paint bounds of the element that is being filtered, in filter
   * space.
   */
  nsIntRect               mTargetBounds;

  /**
   * If set, this is the filter space bounds of the outer-<svg> device space
   * bounds of the dirty area that needs to be repainted. (As bounds-of-bounds,
   * this may be a fair bit bigger than we actually need, unfortunately.)
   */
  nsIntRect               mPostFilterDirtyRect;

  /**
   * If set, this is the filter space bounds of the outer-<svg> device bounds
   * of the pre-filter area of the filtered element that changed. (As
   * bounds-of-bounds, this may be a fair bit bigger than we actually need,
   * unfortunately.)
   */
  nsIntRect               mPreFilterDirtyRect;

  /**
   * The 'primitiveUnits' attribute value (objectBoundingBox or userSpaceOnUse).
   */
  uint16_t                mPrimitiveUnits;

  PrimitiveInfo           mSourceColorAlpha;
  PrimitiveInfo           mSourceAlpha;
  PrimitiveInfo           mFillPaint;
  PrimitiveInfo           mStrokePaint;
  nsTArray<PrimitiveInfo> mPrimitives;
  nsIFrame*               mTransformRoot;
};

#endif
