gfx/src/FilterSupport.cpp
author Ryan VanderMeulen <ryanvm@gmail.com>
Fri, 01 Sep 2017 15:37:08 -0400
changeset 430269 1d02aa80f2f8c283428f0a8a6310e7d1fec3936c
parent 417437 04d0da68636e51e2a03f10a635f12cc1d29b67e1
child 442275 5f74d262924171a8fa0b4483a64f4cac34c4c9b8
permissions -rw-r--r--
Backed out 9 changesets (bug 1383880) for decision task bustage. Backed out changeset 53f5d47a7cb0 (bug 1383880) Backed out changeset a0abda41172a (bug 1383880) Backed out changeset 729a7e2091e8 (bug 1383880) Backed out changeset a33f5a14a471 (bug 1383880) Backed out changeset 5b10d321cfee (bug 1383880) Backed out changeset 8056488d8aed (bug 1383880) Backed out changeset e62c90e3c1e8 (bug 1383880) Backed out changeset 91f116ce6c2a (bug 1383880) Backed out changeset 045498bc36c4 (bug 1383880)

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

#include "FilterSupport.h"

#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Filters.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/PodOperations.h"

#include "gfxContext.h"
#include "gfxPattern.h"
#include "gfxPlatform.h"
#include "gfx2DGlue.h"

#include "nsMargin.h"

// c = n / 255
// c <= 0.0031308f ? c * 12.92f : 1.055f * powf(c, 1 / 2.4f) - 0.055f
static const float glinearRGBTosRGBMap[256] = {
  0.000f, 0.050f, 0.085f, 0.111f, 0.132f, 0.150f, 0.166f, 0.181f,
  0.194f, 0.207f, 0.219f, 0.230f, 0.240f, 0.250f, 0.260f, 0.269f,
  0.278f, 0.286f, 0.295f, 0.303f, 0.310f, 0.318f, 0.325f, 0.332f,
  0.339f, 0.346f, 0.352f, 0.359f, 0.365f, 0.371f, 0.378f, 0.383f,
  0.389f, 0.395f, 0.401f, 0.406f, 0.412f, 0.417f, 0.422f, 0.427f,
  0.433f, 0.438f, 0.443f, 0.448f, 0.452f, 0.457f, 0.462f, 0.466f,
  0.471f, 0.476f, 0.480f, 0.485f, 0.489f, 0.493f, 0.498f, 0.502f,
  0.506f, 0.510f, 0.514f, 0.518f, 0.522f, 0.526f, 0.530f, 0.534f,
  0.538f, 0.542f, 0.546f, 0.549f, 0.553f, 0.557f, 0.561f, 0.564f,
  0.568f, 0.571f, 0.575f, 0.579f, 0.582f, 0.586f, 0.589f, 0.592f,
  0.596f, 0.599f, 0.603f, 0.606f, 0.609f, 0.613f, 0.616f, 0.619f,
  0.622f, 0.625f, 0.629f, 0.632f, 0.635f, 0.638f, 0.641f, 0.644f,
  0.647f, 0.650f, 0.653f, 0.656f, 0.659f, 0.662f, 0.665f, 0.668f,
  0.671f, 0.674f, 0.677f, 0.680f, 0.683f, 0.685f, 0.688f, 0.691f,
  0.694f, 0.697f, 0.699f, 0.702f, 0.705f, 0.708f, 0.710f, 0.713f,
  0.716f, 0.718f, 0.721f, 0.724f, 0.726f, 0.729f, 0.731f, 0.734f,
  0.737f, 0.739f, 0.742f, 0.744f, 0.747f, 0.749f, 0.752f, 0.754f,
  0.757f, 0.759f, 0.762f, 0.764f, 0.767f, 0.769f, 0.772f, 0.774f,
  0.776f, 0.779f, 0.781f, 0.784f, 0.786f, 0.788f, 0.791f, 0.793f,
  0.795f, 0.798f, 0.800f, 0.802f, 0.805f, 0.807f, 0.809f, 0.812f,
  0.814f, 0.816f, 0.818f, 0.821f, 0.823f, 0.825f, 0.827f, 0.829f,
  0.832f, 0.834f, 0.836f, 0.838f, 0.840f, 0.843f, 0.845f, 0.847f,
  0.849f, 0.851f, 0.853f, 0.855f, 0.857f, 0.860f, 0.862f, 0.864f,
  0.866f, 0.868f, 0.870f, 0.872f, 0.874f, 0.876f, 0.878f, 0.880f,
  0.882f, 0.884f, 0.886f, 0.888f, 0.890f, 0.892f, 0.894f, 0.896f,
  0.898f, 0.900f, 0.902f, 0.904f, 0.906f, 0.908f, 0.910f, 0.912f,
  0.914f, 0.916f, 0.918f, 0.920f, 0.922f, 0.924f, 0.926f, 0.928f,
  0.930f, 0.931f, 0.933f, 0.935f, 0.937f, 0.939f, 0.941f, 0.943f,
  0.945f, 0.946f, 0.948f, 0.950f, 0.952f, 0.954f, 0.956f, 0.957f,
  0.959f, 0.961f, 0.963f, 0.965f, 0.967f, 0.968f, 0.970f, 0.972f,
  0.974f, 0.975f, 0.977f, 0.979f, 0.981f, 0.983f, 0.984f, 0.986f,
  0.988f, 0.990f, 0.991f, 0.993f, 0.995f, 0.997f, 0.998f, 1.000f
};

// c = n / 255
// c <= 0.04045f ? c / 12.92f : powf((c + 0.055f) / 1.055f, 2.4f)
static const float gsRGBToLinearRGBMap[256] = {
  0.000f, 0.000f, 0.001f, 0.001f, 0.001f, 0.002f, 0.002f, 0.002f,
  0.002f, 0.003f, 0.003f, 0.003f, 0.004f, 0.004f, 0.004f, 0.005f,
  0.005f, 0.006f, 0.006f, 0.007f, 0.007f, 0.007f, 0.008f, 0.009f,
  0.009f, 0.010f, 0.010f, 0.011f, 0.012f, 0.012f, 0.013f, 0.014f,
  0.014f, 0.015f, 0.016f, 0.017f, 0.018f, 0.019f, 0.019f, 0.020f,
  0.021f, 0.022f, 0.023f, 0.024f, 0.025f, 0.026f, 0.027f, 0.028f,
  0.030f, 0.031f, 0.032f, 0.033f, 0.034f, 0.036f, 0.037f, 0.038f,
  0.040f, 0.041f, 0.042f, 0.044f, 0.045f, 0.047f, 0.048f, 0.050f,
  0.051f, 0.053f, 0.054f, 0.056f, 0.058f, 0.060f, 0.061f, 0.063f,
  0.065f, 0.067f, 0.068f, 0.070f, 0.072f, 0.074f, 0.076f, 0.078f,
  0.080f, 0.082f, 0.084f, 0.087f, 0.089f, 0.091f, 0.093f, 0.095f,
  0.098f, 0.100f, 0.102f, 0.105f, 0.107f, 0.109f, 0.112f, 0.114f,
  0.117f, 0.120f, 0.122f, 0.125f, 0.127f, 0.130f, 0.133f, 0.136f,
  0.138f, 0.141f, 0.144f, 0.147f, 0.150f, 0.153f, 0.156f, 0.159f,
  0.162f, 0.165f, 0.168f, 0.171f, 0.175f, 0.178f, 0.181f, 0.184f,
  0.188f, 0.191f, 0.195f, 0.198f, 0.202f, 0.205f, 0.209f, 0.212f,
  0.216f, 0.220f, 0.223f, 0.227f, 0.231f, 0.235f, 0.238f, 0.242f,
  0.246f, 0.250f, 0.254f, 0.258f, 0.262f, 0.266f, 0.270f, 0.275f,
  0.279f, 0.283f, 0.287f, 0.292f, 0.296f, 0.301f, 0.305f, 0.309f,
  0.314f, 0.319f, 0.323f, 0.328f, 0.332f, 0.337f, 0.342f, 0.347f,
  0.352f, 0.356f, 0.361f, 0.366f, 0.371f, 0.376f, 0.381f, 0.386f,
  0.392f, 0.397f, 0.402f, 0.407f, 0.413f, 0.418f, 0.423f, 0.429f,
  0.434f, 0.440f, 0.445f, 0.451f, 0.456f, 0.462f, 0.468f, 0.474f,
  0.479f, 0.485f, 0.491f, 0.497f, 0.503f, 0.509f, 0.515f, 0.521f,
  0.527f, 0.533f, 0.539f, 0.546f, 0.552f, 0.558f, 0.565f, 0.571f,
  0.578f, 0.584f, 0.591f, 0.597f, 0.604f, 0.610f, 0.617f, 0.624f,
  0.631f, 0.638f, 0.644f, 0.651f, 0.658f, 0.665f, 0.672f, 0.680f,
  0.687f, 0.694f, 0.701f, 0.708f, 0.716f, 0.723f, 0.730f, 0.738f,
  0.745f, 0.753f, 0.761f, 0.768f, 0.776f, 0.784f, 0.791f, 0.799f,
  0.807f, 0.815f, 0.823f, 0.831f, 0.839f, 0.847f, 0.855f, 0.863f,
  0.871f, 0.880f, 0.888f, 0.896f, 0.905f, 0.913f, 0.922f, 0.930f,
  0.939f, 0.947f, 0.956f, 0.965f, 0.973f, 0.982f, 0.991f, 1.000f
};

namespace mozilla {
namespace gfx {

// Some convenience FilterNode creation functions.

namespace FilterWrappers {

  static already_AddRefed<FilterNode>
  Unpremultiply(DrawTarget* aDT, FilterNode* aInput)
  {
    RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::UNPREMULTIPLY);
    if (filter) {
      filter->SetInput(IN_UNPREMULTIPLY_IN, aInput);
      return filter.forget();
    }
    return nullptr;
  }

  static already_AddRefed<FilterNode>
  Premultiply(DrawTarget* aDT, FilterNode* aInput)
  {
    RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::PREMULTIPLY);
    if (filter) {
      filter->SetInput(IN_PREMULTIPLY_IN, aInput);
      return filter.forget();
    }
    return nullptr;
  }

  static already_AddRefed<FilterNode>
  LinearRGBToSRGB(DrawTarget* aDT, FilterNode* aInput)
  {
    RefPtr<FilterNode> transfer = aDT->CreateFilter(FilterType::DISCRETE_TRANSFER);
    if (transfer) {
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_R, false);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_R, glinearRGBTosRGBMap, 256);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_G, false);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_G, glinearRGBTosRGBMap, 256);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_B, false);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_B, glinearRGBTosRGBMap, 256);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_A, true);
      transfer->SetInput(IN_DISCRETE_TRANSFER_IN, aInput);
      return transfer.forget();
    }
    return nullptr;
  }

  static already_AddRefed<FilterNode>
  SRGBToLinearRGB(DrawTarget* aDT, FilterNode* aInput)
  {
    RefPtr<FilterNode> transfer = aDT->CreateFilter(FilterType::DISCRETE_TRANSFER);
    if (transfer) {
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_R, false);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_R, gsRGBToLinearRGBMap, 256);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_G, false);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_G, gsRGBToLinearRGBMap, 256);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_B, false);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_B, gsRGBToLinearRGBMap, 256);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_A, true);
      transfer->SetInput(IN_DISCRETE_TRANSFER_IN, aInput);
      return transfer.forget();
    }
    return nullptr;
  }

  static already_AddRefed<FilterNode>
  Crop(DrawTarget* aDT, FilterNode* aInputFilter, const IntRect& aRect)
  {
    RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::CROP);
    if (filter) {
      filter->SetAttribute(ATT_CROP_RECT, Rect(aRect));
      filter->SetInput(IN_CROP_IN, aInputFilter);
      return filter.forget();
    }
    return nullptr;
  }

  static already_AddRefed<FilterNode>
  Offset(DrawTarget* aDT, FilterNode* aInputFilter, const IntPoint& aOffset)
  {
    RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::TRANSFORM);
    if (filter) {
      filter->SetAttribute(ATT_TRANSFORM_MATRIX, Matrix::Translation(aOffset.x, aOffset.y));
      filter->SetInput(IN_TRANSFORM_IN, aInputFilter);
      return filter.forget();
    }
    return nullptr;
  }

  static already_AddRefed<FilterNode>
  GaussianBlur(DrawTarget* aDT, FilterNode* aInputFilter, const Size& aStdDeviation)
  {
    float stdX = float(std::min(aStdDeviation.width, kMaxStdDeviation));
    float stdY = float(std::min(aStdDeviation.height, kMaxStdDeviation));
    if (stdX == stdY) {
      RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::GAUSSIAN_BLUR);
      if (filter) {
        filter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdX);
        filter->SetInput(IN_GAUSSIAN_BLUR_IN, aInputFilter);
        return filter.forget();
      }
      return nullptr;
    }
    RefPtr<FilterNode> filterH = aDT->CreateFilter(FilterType::DIRECTIONAL_BLUR);
    RefPtr<FilterNode> filterV = aDT->CreateFilter(FilterType::DIRECTIONAL_BLUR);
    if (filterH && filterV) {
      filterH->SetAttribute(ATT_DIRECTIONAL_BLUR_DIRECTION, (uint32_t)BLUR_DIRECTION_X);
      filterH->SetAttribute(ATT_DIRECTIONAL_BLUR_STD_DEVIATION, stdX);
      filterV->SetAttribute(ATT_DIRECTIONAL_BLUR_DIRECTION, (uint32_t)BLUR_DIRECTION_Y);
      filterV->SetAttribute(ATT_DIRECTIONAL_BLUR_STD_DEVIATION, stdY);
      filterH->SetInput(IN_DIRECTIONAL_BLUR_IN, aInputFilter);
      filterV->SetInput(IN_DIRECTIONAL_BLUR_IN, filterH);
      return filterV.forget();
    }
    return nullptr;
  }

  static already_AddRefed<FilterNode>
  Clear(DrawTarget* aDT)
  {
    RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::FLOOD);
    if (filter) {
      filter->SetAttribute(ATT_FLOOD_COLOR, Color(0, 0, 0, 0));
      return filter.forget();
    }
    return nullptr;
  }

  static already_AddRefed<FilterNode>
  ForSurface(DrawTarget* aDT, SourceSurface* aSurface,
             const IntPoint& aSurfacePosition)
  {
    RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::TRANSFORM);
    if (filter) {
      filter->SetAttribute(ATT_TRANSFORM_MATRIX,
        Matrix::Translation(aSurfacePosition.x, aSurfacePosition.y));
      filter->SetInput(IN_TRANSFORM_IN, aSurface);
      return filter.forget();
    }
    return nullptr;
  }

  static already_AddRefed<FilterNode>
  ToAlpha(DrawTarget* aDT, FilterNode* aInput)
  {
    float zero = 0.0f;
    RefPtr<FilterNode> transfer = aDT->CreateFilter(FilterType::DISCRETE_TRANSFER);
    if (transfer) {
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_R, false);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_R, &zero, 1);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_G, false);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_G, &zero, 1);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_B, false);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_TABLE_B, &zero, 1);
      transfer->SetAttribute(ATT_DISCRETE_TRANSFER_DISABLE_A, true);
      transfer->SetInput(IN_DISCRETE_TRANSFER_IN, aInput);
      return transfer.forget();
    }
    return nullptr;
  }

} // namespace FilterWrappers

// A class that wraps a FilterNode and handles conversion between different
// color models. Create FilterCachedColorModels with your original filter and
// the color model that this filter outputs in natively, and then call
// ->ForColorModel(colorModel) in order to get a FilterNode which outputs to
// the specified colorModel.
// Internally, this is achieved by wrapping the original FilterNode with
// conversion FilterNodes. These filter nodes are cached in such a way that no
// repeated or back-and-forth conversions happen.
class FilterCachedColorModels
{
public:
  NS_INLINE_DECL_REFCOUNTING(FilterCachedColorModels)
  // aFilter can be null. In that case, ForColorModel will return a non-null
  // completely transparent filter for all color models.
  FilterCachedColorModels(DrawTarget* aDT,
                          FilterNode* aFilter,
                          ColorModel aOriginalColorModel);

  // Get a FilterNode for the specified color model, guaranteed to be non-null.
  already_AddRefed<FilterNode> ForColorModel(ColorModel aColorModel);

  AlphaModel OriginalAlphaModel() const { return mOriginalColorModel.mAlphaModel; }

private:
  // Create the required FilterNode that will be cached by ForColorModel.
  already_AddRefed<FilterNode> WrapForColorModel(ColorModel aColorModel);

  RefPtr<DrawTarget> mDT;
  ColorModel mOriginalColorModel;

  // This array is indexed by ColorModel::ToIndex.
  RefPtr<FilterNode> mFilterForColorModel[4];

  ~FilterCachedColorModels() {}
};

FilterCachedColorModels::FilterCachedColorModels(DrawTarget* aDT,
                                                 FilterNode* aFilter,
                                                 ColorModel aOriginalColorModel)
 : mDT(aDT)
 , mOriginalColorModel(aOriginalColorModel)
{
  if (aFilter) {
    mFilterForColorModel[aOriginalColorModel.ToIndex()] = aFilter;
  } else {
    RefPtr<FilterNode> clear = FilterWrappers::Clear(aDT);
    mFilterForColorModel[0] = clear;
    mFilterForColorModel[1] = clear;
    mFilterForColorModel[2] = clear;
    mFilterForColorModel[3] = clear;
  }
}

already_AddRefed<FilterNode>
FilterCachedColorModels::ForColorModel(ColorModel aColorModel)
{
  if (aColorModel == mOriginalColorModel) {
    // Make sure to not call WrapForColorModel if our original filter node was
    // null, because then we'd get an infinite recursion.
    RefPtr<FilterNode> filter = mFilterForColorModel[mOriginalColorModel.ToIndex()];
    return filter.forget();
  }

  if (!mFilterForColorModel[aColorModel.ToIndex()]) {
    mFilterForColorModel[aColorModel.ToIndex()] = WrapForColorModel(aColorModel);
  }
  RefPtr<FilterNode> filter(mFilterForColorModel[aColorModel.ToIndex()]);
  return filter.forget();
}

already_AddRefed<FilterNode>
FilterCachedColorModels::WrapForColorModel(ColorModel aColorModel)
{
  // Convert one aspect at a time and recurse.
  // Conversions between premultiplied / unpremultiplied color channels for the
  // same color space can happen directly.
  // Conversions between different color spaces can only happen on
  // unpremultiplied color channels.

  if (aColorModel.mAlphaModel == AlphaModel::Premultiplied) {
    RefPtr<FilterNode> unpre =
      ForColorModel(ColorModel(aColorModel.mColorSpace, AlphaModel::Unpremultiplied));
    return FilterWrappers::Premultiply(mDT, unpre);
  }

  MOZ_ASSERT(aColorModel.mAlphaModel == AlphaModel::Unpremultiplied);
  if (aColorModel.mColorSpace == mOriginalColorModel.mColorSpace) {
    RefPtr<FilterNode> premultiplied =
      ForColorModel(ColorModel(aColorModel.mColorSpace, AlphaModel::Premultiplied));
    return FilterWrappers::Unpremultiply(mDT, premultiplied);
  }

  RefPtr<FilterNode> unpremultipliedOriginal =
    ForColorModel(ColorModel(mOriginalColorModel.mColorSpace, AlphaModel::Unpremultiplied));
  if (aColorModel.mColorSpace == ColorSpace::LinearRGB) {
    return FilterWrappers::SRGBToLinearRGB(mDT, unpremultipliedOriginal);
  }
  return FilterWrappers::LinearRGBToSRGB(mDT, unpremultipliedOriginal);
}

static const float identityMatrix[] =
  { 1, 0, 0, 0, 0,
    0, 1, 0, 0, 0,
    0, 0, 1, 0, 0,
    0, 0, 0, 1, 0 };

// When aAmount == 0, the identity matrix is returned.
// When aAmount == 1, aToMatrix is returned.
// When aAmount > 1, an exaggerated version of aToMatrix is returned. This can
// be useful in certain cases, such as producing a color matrix to oversaturate
// an image.
//
// This function is a shortcut of a full matrix addition and a scalar multiply,
// and it assumes that the following elements in aToMatrix are 0 and 1:
//   x x x 0 0
//   x x x 0 0
//   x x x 0 0
//   0 0 0 1 0
static void
InterpolateFromIdentityMatrix(const float aToMatrix[20], float aAmount,
                              float aOutMatrix[20])
{
  PodCopy(aOutMatrix, identityMatrix, 20);

  float oneMinusAmount = 1 - aAmount;

  aOutMatrix[0] = aAmount * aToMatrix[0] + oneMinusAmount;
  aOutMatrix[1] = aAmount * aToMatrix[1];
  aOutMatrix[2] = aAmount * aToMatrix[2];

  aOutMatrix[5] = aAmount * aToMatrix[5];
  aOutMatrix[6] = aAmount * aToMatrix[6] + oneMinusAmount;
  aOutMatrix[7] = aAmount * aToMatrix[7];

  aOutMatrix[10] = aAmount * aToMatrix[10];
  aOutMatrix[11] = aAmount * aToMatrix[11];
  aOutMatrix[12] = aAmount * aToMatrix[12] + oneMinusAmount;
}

// Create a 4x5 color matrix for the different ways to specify color matrices
// in SVG.
static nsresult
ComputeColorMatrix(uint32_t aColorMatrixType, const nsTArray<float>& aValues,
                   float aOutMatrix[20])
{
  // Luminance coefficients.
  static const float lumR = 0.2126f;
  static const float lumG = 0.7152f;
  static const float lumB = 0.0722f;

  static const float oneMinusLumR = 1 - lumR;
  static const float oneMinusLumG = 1 - lumG;
  static const float oneMinusLumB = 1 - lumB;

  static const float luminanceToAlphaMatrix[] =
    { 0,    0,    0,    0, 0,
      0,    0,    0,    0, 0,
      0,    0,    0,    0, 0,
      lumR, lumG, lumB, 0, 0 };

  static const float saturateMatrix[] =
    { lumR, lumG, lumB, 0, 0,
      lumR, lumG, lumB, 0, 0,
      lumR, lumG, lumB, 0, 0,
      0,    0,    0,    1, 0 };

  static const float sepiaMatrix[] =
    { 0.393f, 0.769f, 0.189f, 0, 0,
      0.349f, 0.686f, 0.168f, 0, 0,
      0.272f, 0.534f, 0.131f, 0, 0,
      0,      0,      0,      1, 0 };

  // Hue rotate specific coefficients.
  static const float hueRotateR = 0.143f;
  static const float hueRotateG = 0.140f;
  static const float hueRotateB = 0.283f;

  switch (aColorMatrixType) {

    case SVG_FECOLORMATRIX_TYPE_MATRIX:
    {
      if (aValues.Length() != 20) {
        return NS_ERROR_FAILURE;
      }

      PodCopy(aOutMatrix, aValues.Elements(), 20);
      break;
    }

    case SVG_FECOLORMATRIX_TYPE_SATURATE:
    {
      if (aValues.Length() != 1)
        return NS_ERROR_FAILURE;

      float s = aValues[0];

      if (s < 0)
        return NS_ERROR_FAILURE;

      InterpolateFromIdentityMatrix(saturateMatrix, 1 - s, aOutMatrix);
      break;
    }

    case SVG_FECOLORMATRIX_TYPE_HUE_ROTATE:
    {
      if (aValues.Length() != 1)
        return NS_ERROR_FAILURE;

      PodCopy(aOutMatrix, identityMatrix, 20);

      float hueRotateValue = aValues[0];

      float c = static_cast<float>(cos(hueRotateValue * M_PI / 180));
      float s = static_cast<float>(sin(hueRotateValue * M_PI / 180));

      aOutMatrix[0] = lumR + oneMinusLumR * c - lumR * s;
      aOutMatrix[1] = lumG - lumG * c - lumG * s;
      aOutMatrix[2] = lumB - lumB * c + oneMinusLumB * s;

      aOutMatrix[5] = lumR - lumR * c + hueRotateR * s;
      aOutMatrix[6] = lumG + oneMinusLumG * c + hueRotateG * s;
      aOutMatrix[7] = lumB - lumB * c - hueRotateB * s;

      aOutMatrix[10] = lumR - lumR * c - oneMinusLumR * s;
      aOutMatrix[11] = lumG - lumG * c + lumG * s;
      aOutMatrix[12] = lumB + oneMinusLumB * c + lumB * s;

      break;
    }

    case SVG_FECOLORMATRIX_TYPE_LUMINANCE_TO_ALPHA:
    {
      PodCopy(aOutMatrix, luminanceToAlphaMatrix, 20);
      break;
    }

    case SVG_FECOLORMATRIX_TYPE_SEPIA:
    {
      if (aValues.Length() != 1)
        return NS_ERROR_FAILURE;

      float amount = aValues[0];

      if (amount < 0 || amount > 1)
        return NS_ERROR_FAILURE;

      InterpolateFromIdentityMatrix(sepiaMatrix, amount, aOutMatrix);
      break;
    }

    default:
      return NS_ERROR_FAILURE;

  }

  return NS_OK;
}

static void
DisableAllTransfers(FilterNode* aTransferFilterNode)
{
  aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_R, true);
  aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_G, true);
  aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_B, true);
  aTransferFilterNode->SetAttribute(ATT_TRANSFER_DISABLE_A, true);
}

// Called for one channel at a time.
// This function creates the required FilterNodes on demand and tries to
// merge conversions of different channels into the same FilterNode if
// possible.
// There's a mismatch between the way SVG and the Moz2D API handle transfer
// functions: In SVG, it's possible to specify a different transfer function
// type for each color channel, but in Moz2D, a given transfer function type
// applies to all color channels.
//
//  @param aFunctionAttributes The attributes of the transfer function for this
//                             channel.
//  @param aChannel The color channel that this function applies to, where
//                  0 = red, 1 = green, 2 = blue, 3 = alpha
//  @param aDT The DrawTarget that the FilterNodes should be created for.
//  @param aTableTransfer Existing FilterNode holders (which may still be
//                        null) that the resulting FilterNodes from this
//                        function will be stored in.
//           
static void
ConvertComponentTransferFunctionToFilter(const AttributeMap& aFunctionAttributes,
                                         int32_t aChannel,
                                         DrawTarget* aDT,
                                         RefPtr<FilterNode>& aTableTransfer,
                                         RefPtr<FilterNode>& aDiscreteTransfer,
                                         RefPtr<FilterNode>& aLinearTransfer,
                                         RefPtr<FilterNode>& aGammaTransfer)
{
  static const TransferAtts disableAtt[4] = {
    ATT_TRANSFER_DISABLE_R,
    ATT_TRANSFER_DISABLE_G,
    ATT_TRANSFER_DISABLE_B,
    ATT_TRANSFER_DISABLE_A
  };

  RefPtr<FilterNode> filter;

  uint32_t type = aFunctionAttributes.GetUint(eComponentTransferFunctionType);

  switch (type) {
  case SVG_FECOMPONENTTRANSFER_TYPE_TABLE:
  {
    const nsTArray<float>& tableValues =
      aFunctionAttributes.GetFloats(eComponentTransferFunctionTableValues);
    if (tableValues.Length() < 2)
      return;

    if (!aTableTransfer) {
      aTableTransfer = aDT->CreateFilter(FilterType::TABLE_TRANSFER);
      if (!aTableTransfer) {
        return;
      }
      DisableAllTransfers(aTableTransfer);
    }
    filter = aTableTransfer;
    static const TableTransferAtts tableAtt[4] = {
      ATT_TABLE_TRANSFER_TABLE_R,
      ATT_TABLE_TRANSFER_TABLE_G,
      ATT_TABLE_TRANSFER_TABLE_B,
      ATT_TABLE_TRANSFER_TABLE_A
    };
    filter->SetAttribute(disableAtt[aChannel], false);
    filter->SetAttribute(tableAtt[aChannel], &tableValues[0], tableValues.Length());
    break;
  }

  case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE:
  {
    const nsTArray<float>& tableValues =
      aFunctionAttributes.GetFloats(eComponentTransferFunctionTableValues);
    if (tableValues.Length() < 1)
      return;

    if (!aDiscreteTransfer) {
      aDiscreteTransfer = aDT->CreateFilter(FilterType::DISCRETE_TRANSFER);
      if (!aDiscreteTransfer) {
        return;
      }
      DisableAllTransfers(aDiscreteTransfer);
    }
    filter = aDiscreteTransfer;
    static const DiscreteTransferAtts tableAtt[4] = {
      ATT_DISCRETE_TRANSFER_TABLE_R,
      ATT_DISCRETE_TRANSFER_TABLE_G,
      ATT_DISCRETE_TRANSFER_TABLE_B,
      ATT_DISCRETE_TRANSFER_TABLE_A
    };
    filter->SetAttribute(disableAtt[aChannel], false);
    filter->SetAttribute(tableAtt[aChannel], &tableValues[0], tableValues.Length());

    break;
  }

  case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR:
  {
    static const LinearTransferAtts slopeAtt[4] = {
      ATT_LINEAR_TRANSFER_SLOPE_R,
      ATT_LINEAR_TRANSFER_SLOPE_G,
      ATT_LINEAR_TRANSFER_SLOPE_B,
      ATT_LINEAR_TRANSFER_SLOPE_A
    };
    static const LinearTransferAtts interceptAtt[4] = {
      ATT_LINEAR_TRANSFER_INTERCEPT_R,
      ATT_LINEAR_TRANSFER_INTERCEPT_G,
      ATT_LINEAR_TRANSFER_INTERCEPT_B,
      ATT_LINEAR_TRANSFER_INTERCEPT_A
    };
    if (!aLinearTransfer) {
      aLinearTransfer = aDT->CreateFilter(FilterType::LINEAR_TRANSFER);
      if (!aLinearTransfer) {
        return;
      }
      DisableAllTransfers(aLinearTransfer);
    }
    filter = aLinearTransfer;
    filter->SetAttribute(disableAtt[aChannel], false);
    float slope = aFunctionAttributes.GetFloat(eComponentTransferFunctionSlope);
    float intercept = aFunctionAttributes.GetFloat(eComponentTransferFunctionIntercept);
    filter->SetAttribute(slopeAtt[aChannel], slope);
    filter->SetAttribute(interceptAtt[aChannel], intercept);
    break;
  }

  case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA:
  {
    static const GammaTransferAtts amplitudeAtt[4] = {
      ATT_GAMMA_TRANSFER_AMPLITUDE_R,
      ATT_GAMMA_TRANSFER_AMPLITUDE_G,
      ATT_GAMMA_TRANSFER_AMPLITUDE_B,
      ATT_GAMMA_TRANSFER_AMPLITUDE_A
    };
    static const GammaTransferAtts exponentAtt[4] = {
      ATT_GAMMA_TRANSFER_EXPONENT_R,
      ATT_GAMMA_TRANSFER_EXPONENT_G,
      ATT_GAMMA_TRANSFER_EXPONENT_B,
      ATT_GAMMA_TRANSFER_EXPONENT_A
    };
    static const GammaTransferAtts offsetAtt[4] = {
      ATT_GAMMA_TRANSFER_OFFSET_R,
      ATT_GAMMA_TRANSFER_OFFSET_G,
      ATT_GAMMA_TRANSFER_OFFSET_B,
      ATT_GAMMA_TRANSFER_OFFSET_A
    };
    if (!aGammaTransfer) {
      aGammaTransfer = aDT->CreateFilter(FilterType::GAMMA_TRANSFER);
      if (!aGammaTransfer) {
        return;
      }
      DisableAllTransfers(aGammaTransfer);
    }
    filter = aGammaTransfer;
    filter->SetAttribute(disableAtt[aChannel], false);
    float amplitude = aFunctionAttributes.GetFloat(eComponentTransferFunctionAmplitude);
    float exponent = aFunctionAttributes.GetFloat(eComponentTransferFunctionExponent);
    float offset = aFunctionAttributes.GetFloat(eComponentTransferFunctionOffset);
    filter->SetAttribute(amplitudeAtt[aChannel], amplitude);
    filter->SetAttribute(exponentAtt[aChannel], exponent);
    filter->SetAttribute(offsetAtt[aChannel], offset);
    break;
  }

  case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
  default:
    break;
  }
}

const int32_t kMorphologyMaxRadius = 100000;

// Handle the different primitive description types and create the necessary
// FilterNode(s) for each.
// Returns nullptr for invalid filter primitives. This should be interpreted as
// transparent black by the caller.
// aSourceRegions contains the filter primitive subregions of the source
// primitives; only needed for eTile primitives.
// aInputImages carries additional surfaces that are used by eImage primitives.
static already_AddRefed<FilterNode>
FilterNodeFromPrimitiveDescription(const FilterPrimitiveDescription& aDescription,
                                   DrawTarget* aDT,
                                   nsTArray<RefPtr<FilterNode> >& aSources,
                                   nsTArray<IntRect>& aSourceRegions,
                                   nsTArray<RefPtr<SourceSurface>>& aInputImages)
{
  const AttributeMap& atts = aDescription.Attributes();
  switch (aDescription.Type()) {

    case PrimitiveType::Empty:
      return nullptr;

    case PrimitiveType::Blend:
    {
      uint32_t mode = atts.GetUint(eBlendBlendmode);
      RefPtr<FilterNode> filter;
      if (mode == SVG_FEBLEND_MODE_UNKNOWN) {
        return nullptr;
      }
      if (mode == SVG_FEBLEND_MODE_NORMAL) {
        filter = aDT->CreateFilter(FilterType::COMPOSITE);
        if (!filter) {
          return nullptr;
        }
        filter->SetInput(IN_COMPOSITE_IN_START, aSources[1]);
        filter->SetInput(IN_COMPOSITE_IN_START + 1, aSources[0]);
      } else {
        filter = aDT->CreateFilter(FilterType::BLEND);
        if (!filter) {
          return nullptr;
        }
        static const uint8_t blendModes[SVG_FEBLEND_MODE_LUMINOSITY + 1] = {
          0,
          0,
          BLEND_MODE_MULTIPLY,
          BLEND_MODE_SCREEN,
          BLEND_MODE_DARKEN,
          BLEND_MODE_LIGHTEN,
          BLEND_MODE_OVERLAY,
          BLEND_MODE_COLOR_DODGE,
          BLEND_MODE_COLOR_BURN,
          BLEND_MODE_HARD_LIGHT,
          BLEND_MODE_SOFT_LIGHT,
          BLEND_MODE_DIFFERENCE,
          BLEND_MODE_EXCLUSION,
          BLEND_MODE_HUE,
          BLEND_MODE_SATURATION,
          BLEND_MODE_COLOR,
          BLEND_MODE_LUMINOSITY
        };
        filter->SetAttribute(ATT_BLEND_BLENDMODE, (uint32_t)blendModes[mode]);
        // The correct input order for both software and D2D filters is flipped
        // from our source order, so flip here.
        filter->SetInput(IN_BLEND_IN, aSources[1]);
        filter->SetInput(IN_BLEND_IN2, aSources[0]);
      }
      return filter.forget();
    }

    case PrimitiveType::ColorMatrix:
    {
      float colorMatrix[20];
      uint32_t type = atts.GetUint(eColorMatrixType);
      const nsTArray<float>& values = atts.GetFloats(eColorMatrixValues);
      if (NS_FAILED(ComputeColorMatrix(type, values, colorMatrix)) ||
          PodEqual(colorMatrix, identityMatrix)) {
        RefPtr<FilterNode> filter(aSources[0]);
        return filter.forget();
      }
      Matrix5x4 matrix(colorMatrix[0], colorMatrix[5], colorMatrix[10],  colorMatrix[15],
                       colorMatrix[1], colorMatrix[6], colorMatrix[11],  colorMatrix[16],
                       colorMatrix[2], colorMatrix[7], colorMatrix[12],  colorMatrix[17],
                       colorMatrix[3], colorMatrix[8], colorMatrix[13],  colorMatrix[18],
                       colorMatrix[4], colorMatrix[9], colorMatrix[14],  colorMatrix[19]);
      RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::COLOR_MATRIX);
      if (!filter) {
        return nullptr;
      }
      filter->SetAttribute(ATT_COLOR_MATRIX_MATRIX, matrix);
      filter->SetAttribute(ATT_COLOR_MATRIX_ALPHA_MODE, (uint32_t)ALPHA_MODE_STRAIGHT);
      filter->SetInput(IN_COLOR_MATRIX_IN, aSources[0]);
      return filter.forget();
    }

    case PrimitiveType::Morphology:
    {
      Size radii = atts.GetSize(eMorphologyRadii);
      int32_t rx = radii.width;
      int32_t ry = radii.height;
      if (rx < 0 || ry < 0) {
        // XXX SVGContentUtils::ReportToConsole()
        return nullptr;
      }
      if (rx == 0 && ry == 0) {
        return nullptr;
      }

      // Clamp radii to prevent completely insane values:
      rx = std::min(rx, kMorphologyMaxRadius);
      ry = std::min(ry, kMorphologyMaxRadius);

      MorphologyOperator op = atts.GetUint(eMorphologyOperator) == SVG_OPERATOR_ERODE ?
        MORPHOLOGY_OPERATOR_ERODE : MORPHOLOGY_OPERATOR_DILATE;

      RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::MORPHOLOGY);
      if (!filter) {
        return nullptr;
      }
      filter->SetAttribute(ATT_MORPHOLOGY_RADII, IntSize(rx, ry));
      filter->SetAttribute(ATT_MORPHOLOGY_OPERATOR, (uint32_t)op);
      filter->SetInput(IN_MORPHOLOGY_IN, aSources[0]);
      return filter.forget();
    }

    case PrimitiveType::Flood:
    {
      Color color = atts.GetColor(eFloodColor);
      RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::FLOOD);
      if (!filter) {
        return nullptr;
      }
      filter->SetAttribute(ATT_FLOOD_COLOR, color);
      return filter.forget();
    }

    case PrimitiveType::Tile:
    {
      RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::TILE);
      if (!filter) {
        return nullptr;
      }
      filter->SetAttribute(ATT_TILE_SOURCE_RECT, aSourceRegions[0]);
      filter->SetInput(IN_TILE_IN, aSources[0]);
      return filter.forget();
    }

    case PrimitiveType::ComponentTransfer:
    {
      RefPtr<FilterNode> filters[4]; // one for each FILTER_*_TRANSFER type
      static const AttributeName componentFunctionNames[4] = {
        eComponentTransferFunctionR,
        eComponentTransferFunctionG,
        eComponentTransferFunctionB,
        eComponentTransferFunctionA
      };
      for (int32_t i = 0; i < 4; i++) {
        AttributeMap functionAttributes =
          atts.GetAttributeMap(componentFunctionNames[i]);
        ConvertComponentTransferFunctionToFilter(functionAttributes, i, aDT,
          filters[0], filters[1], filters[2], filters[3]);
      }

      // Connect all used filters nodes.
      RefPtr<FilterNode> lastFilter = aSources[0];
      for (int32_t i = 0; i < 4; i++) {
        if (filters[i]) {
          filters[i]->SetInput(0, lastFilter);
          lastFilter = filters[i];
        }
      }

      return lastFilter.forget();
    }

    case PrimitiveType::ConvolveMatrix:
    {
      RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::CONVOLVE_MATRIX);
      if (!filter) {
        return nullptr;
      }
      filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_SIZE, atts.GetIntSize(eConvolveMatrixKernelSize));
      const nsTArray<float>& matrix = atts.GetFloats(eConvolveMatrixKernelMatrix);
      filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_MATRIX,
                           matrix.Elements(), matrix.Length());
      filter->SetAttribute(ATT_CONVOLVE_MATRIX_DIVISOR,
                           atts.GetFloat(eConvolveMatrixDivisor));
      filter->SetAttribute(ATT_CONVOLVE_MATRIX_BIAS,
                           atts.GetFloat(eConvolveMatrixBias));
      filter->SetAttribute(ATT_CONVOLVE_MATRIX_TARGET,
                           atts.GetIntPoint(eConvolveMatrixTarget));
      filter->SetAttribute(ATT_CONVOLVE_MATRIX_SOURCE_RECT,
                           aSourceRegions[0]);
      uint32_t edgeMode = atts.GetUint(eConvolveMatrixEdgeMode);
      static const uint8_t edgeModes[SVG_EDGEMODE_NONE+1] = {
        EDGE_MODE_NONE,      // SVG_EDGEMODE_UNKNOWN
        EDGE_MODE_DUPLICATE, // SVG_EDGEMODE_DUPLICATE
        EDGE_MODE_WRAP,      // SVG_EDGEMODE_WRAP
        EDGE_MODE_NONE       // SVG_EDGEMODE_NONE
      };
      filter->SetAttribute(ATT_CONVOLVE_MATRIX_EDGE_MODE, (uint32_t)edgeModes[edgeMode]);
      filter->SetAttribute(ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH,
                           atts.GetSize(eConvolveMatrixKernelUnitLength));
      filter->SetAttribute(ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA,
                           atts.GetBool(eConvolveMatrixPreserveAlpha));
      filter->SetInput(IN_CONVOLVE_MATRIX_IN, aSources[0]);
      return filter.forget();
    }

    case PrimitiveType::Offset:
    {
      return FilterWrappers::Offset(aDT, aSources[0],
                                    atts.GetIntPoint(eOffsetOffset));
    }

    case PrimitiveType::DisplacementMap:
    {
      RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::DISPLACEMENT_MAP);
      if (!filter) {
        return nullptr;
      }
      filter->SetAttribute(ATT_DISPLACEMENT_MAP_SCALE,
                           atts.GetFloat(eDisplacementMapScale));
      static const uint8_t channel[SVG_CHANNEL_A+1] = {
        COLOR_CHANNEL_R, // SVG_CHANNEL_UNKNOWN
        COLOR_CHANNEL_R, // SVG_CHANNEL_R
        COLOR_CHANNEL_G, // SVG_CHANNEL_G
        COLOR_CHANNEL_B, // SVG_CHANNEL_B
        COLOR_CHANNEL_A  // SVG_CHANNEL_A
      };
      filter->SetAttribute(ATT_DISPLACEMENT_MAP_X_CHANNEL,
                           (uint32_t)channel[atts.GetUint(eDisplacementMapXChannel)]);
      filter->SetAttribute(ATT_DISPLACEMENT_MAP_Y_CHANNEL,
                           (uint32_t)channel[atts.GetUint(eDisplacementMapYChannel)]);
      filter->SetInput(IN_DISPLACEMENT_MAP_IN, aSources[0]);
      filter->SetInput(IN_DISPLACEMENT_MAP_IN2, aSources[1]);
      return filter.forget();
    }

    case PrimitiveType::Turbulence:
    {
      RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::TURBULENCE);
      if (!filter) {
        return nullptr;
      }
      filter->SetAttribute(ATT_TURBULENCE_BASE_FREQUENCY,
                           atts.GetSize(eTurbulenceBaseFrequency));
      filter->SetAttribute(ATT_TURBULENCE_NUM_OCTAVES,
                           atts.GetUint(eTurbulenceNumOctaves));
      filter->SetAttribute(ATT_TURBULENCE_STITCHABLE,
                           atts.GetBool(eTurbulenceStitchable));
      filter->SetAttribute(ATT_TURBULENCE_SEED,
                           (uint32_t)atts.GetFloat(eTurbulenceSeed));
      static const uint8_t type[SVG_TURBULENCE_TYPE_TURBULENCE+1] = {
        TURBULENCE_TYPE_FRACTAL_NOISE, // SVG_TURBULENCE_TYPE_UNKNOWN
        TURBULENCE_TYPE_FRACTAL_NOISE, // SVG_TURBULENCE_TYPE_FRACTALNOISE
        TURBULENCE_TYPE_TURBULENCE     // SVG_TURBULENCE_TYPE_TURBULENCE
      };
      filter->SetAttribute(ATT_TURBULENCE_TYPE,
                           (uint32_t)type[atts.GetUint(eTurbulenceType)]);
      filter->SetAttribute(ATT_TURBULENCE_RECT,
                           aDescription.PrimitiveSubregion() - atts.GetIntPoint(eTurbulenceOffset));
      return FilterWrappers::Offset(aDT, filter, atts.GetIntPoint(eTurbulenceOffset));
    }

    case PrimitiveType::Composite:
    {
      RefPtr<FilterNode> filter;
      uint32_t op = atts.GetUint(eCompositeOperator);
      if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) {
        const nsTArray<float>& coefficients = atts.GetFloats(eCompositeCoefficients);
        static const float allZero[4] = { 0, 0, 0, 0 };
        filter = aDT->CreateFilter(FilterType::ARITHMETIC_COMBINE);
        // All-zero coefficients sometimes occur in junk filters.
        if (!filter ||
            (coefficients.Length() == ArrayLength(allZero) &&
             PodEqual(coefficients.Elements(), allZero, ArrayLength(allZero)))) {
          return nullptr;
        }
        filter->SetAttribute(ATT_ARITHMETIC_COMBINE_COEFFICIENTS,
                             coefficients.Elements(), coefficients.Length());
        filter->SetInput(IN_ARITHMETIC_COMBINE_IN, aSources[0]);
        filter->SetInput(IN_ARITHMETIC_COMBINE_IN2, aSources[1]);
      } else {
        filter = aDT->CreateFilter(FilterType::COMPOSITE);
        if (!filter) {
          return nullptr;
        }
        static const uint8_t operators[SVG_FECOMPOSITE_OPERATOR_ARITHMETIC] = {
          COMPOSITE_OPERATOR_OVER, // SVG_FECOMPOSITE_OPERATOR_UNKNOWN
          COMPOSITE_OPERATOR_OVER, // SVG_FECOMPOSITE_OPERATOR_OVER
          COMPOSITE_OPERATOR_IN,   // SVG_FECOMPOSITE_OPERATOR_IN
          COMPOSITE_OPERATOR_OUT,  // SVG_FECOMPOSITE_OPERATOR_OUT
          COMPOSITE_OPERATOR_ATOP, // SVG_FECOMPOSITE_OPERATOR_ATOP
          COMPOSITE_OPERATOR_XOR   // SVG_FECOMPOSITE_OPERATOR_XOR
        };
        filter->SetAttribute(ATT_COMPOSITE_OPERATOR, (uint32_t)operators[op]);
        filter->SetInput(IN_COMPOSITE_IN_START, aSources[1]);
        filter->SetInput(IN_COMPOSITE_IN_START + 1, aSources[0]);
      }
      return filter.forget();
    }

    case PrimitiveType::Merge:
    {
      if (aSources.Length() == 0) {
        return nullptr;
      }
      if (aSources.Length() == 1) {
        RefPtr<FilterNode> filter(aSources[0]);
        return filter.forget();
      }
      RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::COMPOSITE);
      if (!filter) {
        return nullptr;
      }
      filter->SetAttribute(ATT_COMPOSITE_OPERATOR, (uint32_t)COMPOSITE_OPERATOR_OVER);
      for (size_t i = 0; i < aSources.Length(); i++) {
        filter->SetInput(IN_COMPOSITE_IN_START + i, aSources[i]);
      }
      return filter.forget();
    }

    case PrimitiveType::GaussianBlur:
    {
      return FilterWrappers::GaussianBlur(aDT, aSources[0],
                                          atts.GetSize(eGaussianBlurStdDeviation));
    }

    case PrimitiveType::DropShadow:
    {
      RefPtr<FilterNode> alpha = FilterWrappers::ToAlpha(aDT, aSources[0]);
      RefPtr<FilterNode> blur = FilterWrappers::GaussianBlur(aDT, alpha,
                                  atts.GetSize(eDropShadowStdDeviation));
      RefPtr<FilterNode> offsetBlur = FilterWrappers::Offset(aDT, blur,
                                        atts.GetIntPoint(eDropShadowOffset));
      RefPtr<FilterNode> flood = aDT->CreateFilter(FilterType::FLOOD);
      if (!flood) {
        return nullptr;
      }
      Color color = atts.GetColor(eDropShadowColor);
      if (aDescription.InputColorSpace(0) == ColorSpace::LinearRGB) {
        color = Color(gsRGBToLinearRGBMap[uint8_t(color.r * 255)],
                      gsRGBToLinearRGBMap[uint8_t(color.g * 255)],
                      gsRGBToLinearRGBMap[uint8_t(color.b * 255)],
                      color.a);
      }
      flood->SetAttribute(ATT_FLOOD_COLOR, color);

      RefPtr<FilterNode> composite = aDT->CreateFilter(FilterType::COMPOSITE);
      if (!composite) {
        return nullptr;
      }
      composite->SetAttribute(ATT_COMPOSITE_OPERATOR, (uint32_t)COMPOSITE_OPERATOR_IN);
      composite->SetInput(IN_COMPOSITE_IN_START, offsetBlur);
      composite->SetInput(IN_COMPOSITE_IN_START + 1, flood);

      RefPtr<FilterNode> filter = aDT->CreateFilter(FilterType::COMPOSITE);
      if (!filter) {
        return nullptr;
      }
      filter->SetAttribute(ATT_COMPOSITE_OPERATOR, (uint32_t)COMPOSITE_OPERATOR_OVER);
      filter->SetInput(IN_COMPOSITE_IN_START, composite);
      filter->SetInput(IN_COMPOSITE_IN_START + 1, aSources[0]);
      return filter.forget();
    }

    case PrimitiveType::DiffuseLighting:
    case PrimitiveType::SpecularLighting:
    {
      bool isSpecular =
        aDescription.Type() == PrimitiveType::SpecularLighting;

      AttributeMap lightAttributes = atts.GetAttributeMap(eLightingLight);

      if (lightAttributes.GetUint(eLightType) == eLightTypeNone) {
        return nullptr;
      }

      enum { POINT = 0, SPOT, DISTANT } lightType = POINT;

      switch (lightAttributes.GetUint(eLightType)) {
        case eLightTypePoint:   lightType = POINT;   break;
        case eLightTypeSpot:    lightType = SPOT;    break;
        case eLightTypeDistant: lightType = DISTANT; break;
      }

      static const FilterType filterType[2][DISTANT+1] = {
        { FilterType::POINT_DIFFUSE, FilterType::SPOT_DIFFUSE, FilterType::DISTANT_DIFFUSE },
        { FilterType::POINT_SPECULAR, FilterType::SPOT_SPECULAR, FilterType::DISTANT_SPECULAR }
      };
      RefPtr<FilterNode> filter =
        aDT->CreateFilter(filterType[isSpecular][lightType]);
      if (!filter) {
        return nullptr;
      }

      filter->SetAttribute(ATT_LIGHTING_COLOR,
                           atts.GetColor(eLightingColor));
      filter->SetAttribute(ATT_LIGHTING_SURFACE_SCALE,
                           atts.GetFloat(eLightingSurfaceScale));
      filter->SetAttribute(ATT_LIGHTING_KERNEL_UNIT_LENGTH,
                           atts.GetSize(eLightingKernelUnitLength));

      if (isSpecular) {
        filter->SetAttribute(ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT,
                             atts.GetFloat(eSpecularLightingSpecularConstant));
        filter->SetAttribute(ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT,
                             atts.GetFloat(eSpecularLightingSpecularExponent));
      } else {
        filter->SetAttribute(ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT,
                             atts.GetFloat(eDiffuseLightingDiffuseConstant));
      }

      switch (lightType) {
        case POINT:
          filter->SetAttribute(ATT_POINT_LIGHT_POSITION,
                               lightAttributes.GetPoint3D(ePointLightPosition));
          break;
        case SPOT:
          filter->SetAttribute(ATT_SPOT_LIGHT_POSITION,
                               lightAttributes.GetPoint3D(eSpotLightPosition));
          filter->SetAttribute(ATT_SPOT_LIGHT_POINTS_AT,
                               lightAttributes.GetPoint3D(eSpotLightPointsAt));
          filter->SetAttribute(ATT_SPOT_LIGHT_FOCUS,
                               lightAttributes.GetFloat(eSpotLightFocus));
          filter->SetAttribute(ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE,
                               lightAttributes.GetFloat(eSpotLightLimitingConeAngle));
          break;
        case DISTANT:
          filter->SetAttribute(ATT_DISTANT_LIGHT_AZIMUTH,
                               lightAttributes.GetFloat(eDistantLightAzimuth));
          filter->SetAttribute(ATT_DISTANT_LIGHT_ELEVATION,
                               lightAttributes.GetFloat(eDistantLightElevation));
          break;
      }

      filter->SetInput(IN_LIGHTING_IN, aSources[0]);

      return filter.forget();
    }

    case PrimitiveType::Image:
    {
      Matrix TM = atts.GetMatrix(eImageTransform);
      if (!TM.Determinant()) {
        return nullptr;
      }

      // Pull the image from the additional image list using the index that's
      // stored in the primitive description.
      RefPtr<SourceSurface> inputImage =
        aInputImages[atts.GetUint(eImageInputIndex)];

      RefPtr<FilterNode> transform = aDT->CreateFilter(FilterType::TRANSFORM);
      if (!transform) {
        return nullptr;
      }
      transform->SetInput(IN_TRANSFORM_IN, inputImage);
      transform->SetAttribute(ATT_TRANSFORM_MATRIX, TM);
      transform->SetAttribute(ATT_TRANSFORM_FILTER, atts.GetUint(eImageFilter));
      return transform.forget();
    }

    case PrimitiveType::ToAlpha:
    {
      return FilterWrappers::ToAlpha(aDT, aSources[0]);
    }

    default:
      return nullptr;
  }
}

template<typename T>
static const T&
ElementForIndex(int32_t aIndex,
                const nsTArray<T>& aPrimitiveElements,
                const T& aSourceGraphicElement,
                const T& aFillPaintElement,
                const T& aStrokePaintElement)
{
  switch (aIndex) {
    case FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic:
    case FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha:
      return aSourceGraphicElement;
    case FilterPrimitiveDescription::kPrimitiveIndexFillPaint:
      return aFillPaintElement;
    case FilterPrimitiveDescription::kPrimitiveIndexStrokePaint:
      return aStrokePaintElement;
    default:
      MOZ_ASSERT(aIndex >= 0, "bad index");
      return aPrimitiveElements[aIndex];
  }
}

static AlphaModel
InputAlphaModelForPrimitive(const FilterPrimitiveDescription& aDescr,
                            int32_t aInputIndex,
                            AlphaModel aOriginalAlphaModel)
{
  switch (aDescr.Type()) {
    case PrimitiveType::Tile:
    case PrimitiveType::Offset:
    case PrimitiveType::ToAlpha:
      return aOriginalAlphaModel;

    case PrimitiveType::ColorMatrix:
    case PrimitiveType::ComponentTransfer:
      return AlphaModel::Unpremultiplied;

    case PrimitiveType::DisplacementMap:
      return aInputIndex == 0 ?
        AlphaModel::Premultiplied : AlphaModel::Unpremultiplied;

    case PrimitiveType::ConvolveMatrix:
      return aDescr.Attributes().GetBool(eConvolveMatrixPreserveAlpha) ?
        AlphaModel::Unpremultiplied : AlphaModel::Premultiplied;

    default:
      return AlphaModel::Premultiplied;
  }
}

static AlphaModel
OutputAlphaModelForPrimitive(const FilterPrimitiveDescription& aDescr,
                             const nsTArray<AlphaModel>& aInputAlphaModels)
{
  if (aInputAlphaModels.Length()) {
    // For filters with inputs, the output is premultiplied if and only if the
    // first input is premultiplied.
    return InputAlphaModelForPrimitive(aDescr, 0, aInputAlphaModels[0]);
  }

  // All filters without inputs produce premultiplied alpha.
  return AlphaModel::Premultiplied;
}

// Returns the output FilterNode, in premultiplied sRGB space.
static already_AddRefed<FilterNode>
FilterNodeGraphFromDescription(DrawTarget* aDT,
                               const FilterDescription& aFilter,
                               const Rect& aResultNeededRect,
                               SourceSurface* aSourceGraphic,
                               const IntRect& aSourceGraphicRect,
                               SourceSurface* aFillPaint,
                               const IntRect& aFillPaintRect,
                               SourceSurface* aStrokePaint,
                               const IntRect& aStrokePaintRect,
                               nsTArray<RefPtr<SourceSurface>>& aAdditionalImages)
{
  const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives;
  MOZ_RELEASE_ASSERT(!primitives.IsEmpty());

  RefPtr<FilterCachedColorModels> sourceFilters[4];
  nsTArray<RefPtr<FilterCachedColorModels> > primitiveFilters;

  for (size_t i = 0; i < primitives.Length(); ++i) {
    const FilterPrimitiveDescription& descr = primitives[i];

    nsTArray<RefPtr<FilterNode> > inputFilterNodes;
    nsTArray<IntRect> inputSourceRects;
    nsTArray<AlphaModel> inputAlphaModels;

    for (size_t j = 0; j < descr.NumberOfInputs(); j++) {

      int32_t inputIndex = descr.InputPrimitiveIndex(j);
      if (inputIndex < 0) {
        inputSourceRects.AppendElement(descr.FilterSpaceBounds());
      } else {
        inputSourceRects.AppendElement(primitives[inputIndex].PrimitiveSubregion());
      }

      RefPtr<FilterCachedColorModels> inputFilter;
      if (inputIndex >= 0) {
        MOZ_ASSERT(inputIndex < (int64_t)primitiveFilters.Length(), "out-of-bounds input index!");
        inputFilter = primitiveFilters[inputIndex];
        MOZ_ASSERT(inputFilter, "Referred to input filter that comes after the current one?");
      } else {
        int32_t sourceIndex = -inputIndex - 1;
        MOZ_ASSERT(sourceIndex >= 0, "invalid source index");
        MOZ_ASSERT(sourceIndex < 4, "invalid source index");
        inputFilter = sourceFilters[sourceIndex];
        if (!inputFilter) {
          RefPtr<FilterNode> sourceFilterNode;

          nsTArray<SourceSurface*> primitiveSurfaces;
          nsTArray<IntRect> primitiveSurfaceRects;
          RefPtr<SourceSurface> surf =
            ElementForIndex(inputIndex, primitiveSurfaces,
                            aSourceGraphic, aFillPaint, aStrokePaint);
          IntRect surfaceRect =
            ElementForIndex(inputIndex, primitiveSurfaceRects,
                            aSourceGraphicRect, aFillPaintRect, aStrokePaintRect);
          if (surf) {
            IntPoint offset = surfaceRect.TopLeft();
            sourceFilterNode = FilterWrappers::ForSurface(aDT, surf, offset);

            // Clip the original SourceGraphic to the first filter region if the
            // surface isn't already sized appropriately.
            if ((inputIndex == FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic ||
                 inputIndex == FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha) &&
                !descr.FilterSpaceBounds().Contains(aSourceGraphicRect)) {
              sourceFilterNode =
                FilterWrappers::Crop(aDT, sourceFilterNode, descr.FilterSpaceBounds());
            }

            if (inputIndex == FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha) {
              sourceFilterNode = FilterWrappers::ToAlpha(aDT, sourceFilterNode);
            }
          }

          inputFilter = new FilterCachedColorModels(aDT, sourceFilterNode,
                                                    ColorModel::PremulSRGB());
          sourceFilters[sourceIndex] = inputFilter;
        }
      }
      MOZ_ASSERT(inputFilter);

      AlphaModel inputAlphaModel =
        InputAlphaModelForPrimitive(descr, j, inputFilter->OriginalAlphaModel());
      inputAlphaModels.AppendElement(inputAlphaModel);
      ColorModel inputColorModel(descr.InputColorSpace(j), inputAlphaModel);
      inputFilterNodes.AppendElement(inputFilter->ForColorModel(inputColorModel));
    }

    RefPtr<FilterNode> primitiveFilterNode =
      FilterNodeFromPrimitiveDescription(descr, aDT, inputFilterNodes,
                                         inputSourceRects, aAdditionalImages);

    if (primitiveFilterNode) {
      primitiveFilterNode =
        FilterWrappers::Crop(aDT, primitiveFilterNode, descr.PrimitiveSubregion());
    }

    ColorModel outputColorModel(descr.OutputColorSpace(),
      OutputAlphaModelForPrimitive(descr, inputAlphaModels));
    RefPtr<FilterCachedColorModels> primitiveFilter =
      new FilterCachedColorModels(aDT, primitiveFilterNode, outputColorModel);

    primitiveFilters.AppendElement(primitiveFilter);
  }

  MOZ_RELEASE_ASSERT(!primitiveFilters.IsEmpty());
  return primitiveFilters.LastElement()->ForColorModel(ColorModel::PremulSRGB());
}

// FilterSupport

void
FilterSupport::RenderFilterDescription(DrawTarget* aDT,
                                       const FilterDescription& aFilter,
                                       const Rect& aRenderRect,
                                       SourceSurface* aSourceGraphic,
                                       const IntRect& aSourceGraphicRect,
                                       SourceSurface* aFillPaint,
                                       const IntRect& aFillPaintRect,
                                       SourceSurface* aStrokePaint,
                                       const IntRect& aStrokePaintRect,
                                       nsTArray<RefPtr<SourceSurface>>& aAdditionalImages,
                                       const Point& aDestPoint,
                                       const DrawOptions& aOptions)
{
  RefPtr<FilterNode> resultFilter =
    FilterNodeGraphFromDescription(aDT, aFilter, aRenderRect,
                                   aSourceGraphic, aSourceGraphicRect, aFillPaint, aFillPaintRect,
                                   aStrokePaint, aStrokePaintRect, aAdditionalImages);
  if (!resultFilter) {
    gfxWarning() << "Filter is NULL.";
    return;
  }
  aDT->DrawFilter(resultFilter, aRenderRect, aDestPoint, aOptions);
}

static nsIntRegion
UnionOfRegions(const nsTArray<nsIntRegion>& aRegions)
{
  nsIntRegion result;
  for (size_t i = 0; i < aRegions.Length(); i++) {
    result.Or(result, aRegions[i]);
  }
  return result;
}

static int32_t
InflateSizeForBlurStdDev(float aStdDev)
{
  double size = std::min(aStdDev, kMaxStdDeviation) * (3 * sqrt(2 * M_PI) / 4) * 1.5;
  return uint32_t(floor(size + 0.5));
}

static nsIntRegion
ResultChangeRegionForPrimitive(const FilterPrimitiveDescription& aDescription,
                               const nsTArray<nsIntRegion>& aInputChangeRegions)
{
  const AttributeMap& atts = aDescription.Attributes();
  switch (aDescription.Type()) {

    case PrimitiveType::Empty:
    case PrimitiveType::Flood:
    case PrimitiveType::Turbulence:
    case PrimitiveType::Image:
      return nsIntRegion();

    case PrimitiveType::Blend:
    case PrimitiveType::Composite:
    case PrimitiveType::Merge:
      return UnionOfRegions(aInputChangeRegions);

    case PrimitiveType::ColorMatrix:
    case PrimitiveType::ComponentTransfer:
    case PrimitiveType::ToAlpha:
      return aInputChangeRegions[0];

    case PrimitiveType::Morphology:
    {
      Size radii = atts.GetSize(eMorphologyRadii);
      int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius);
      int32_t ry = clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius);
      return aInputChangeRegions[0].Inflated(nsIntMargin(ry, rx, ry, rx));
    }

    case PrimitiveType::Tile:
      return aDescription.PrimitiveSubregion();

    case PrimitiveType::ConvolveMatrix:
    {
      if (atts.GetUint(eConvolveMatrixEdgeMode) != EDGE_MODE_NONE) {
        return aDescription.PrimitiveSubregion();
      }
      Size kernelUnitLength = atts.GetSize(eConvolveMatrixKernelUnitLength);
      IntSize kernelSize = atts.GetIntSize(eConvolveMatrixKernelSize);
      IntPoint target = atts.GetIntPoint(eConvolveMatrixTarget);
      nsIntMargin m(ceil(kernelUnitLength.width * (target.x)),
                    ceil(kernelUnitLength.height * (target.y)),
                    ceil(kernelUnitLength.width * (kernelSize.width - target.x - 1)),
                    ceil(kernelUnitLength.height * (kernelSize.height - target.y - 1)));
      return aInputChangeRegions[0].Inflated(m);
    }

    case PrimitiveType::Offset:
    {
      IntPoint offset = atts.GetIntPoint(eOffsetOffset);
      return aInputChangeRegions[0].MovedBy(offset.x, offset.y);
    }

    case PrimitiveType::DisplacementMap:
    {
      int32_t scale = ceil(std::abs(atts.GetFloat(eDisplacementMapScale)));
      return aInputChangeRegions[0].Inflated(nsIntMargin(scale, scale, scale, scale));
    }

    case PrimitiveType::GaussianBlur:
    {
      Size stdDeviation = atts.GetSize(eGaussianBlurStdDeviation);
      int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width);
      int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height);
      return aInputChangeRegions[0].Inflated(nsIntMargin(dy, dx, dy, dx));
    }

    case PrimitiveType::DropShadow:
    {
      IntPoint offset = atts.GetIntPoint(eDropShadowOffset);
      nsIntRegion offsetRegion = aInputChangeRegions[0].MovedBy(offset.x, offset.y);
      Size stdDeviation = atts.GetSize(eDropShadowStdDeviation);
      int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width);
      int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height);
      nsIntRegion blurRegion = offsetRegion.Inflated(nsIntMargin(dy, dx, dy, dx));
      blurRegion.Or(blurRegion, aInputChangeRegions[0]);
      return blurRegion;
    }

    case PrimitiveType::DiffuseLighting:
    case PrimitiveType::SpecularLighting:
    {
      Size kernelUnitLength = atts.GetSize(eLightingKernelUnitLength);
      int32_t dx = ceil(kernelUnitLength.width);
      int32_t dy = ceil(kernelUnitLength.height);
      return aInputChangeRegions[0].Inflated(nsIntMargin(dy, dx, dy, dx));
    }

    default:
      return nsIntRegion();
  }
}

/* static */ nsIntRegion
FilterSupport::ComputeResultChangeRegion(const FilterDescription& aFilter,
                                         const nsIntRegion& aSourceGraphicChange,
                                         const nsIntRegion& aFillPaintChange,
                                         const nsIntRegion& aStrokePaintChange)
{
  const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives;
  MOZ_RELEASE_ASSERT(!primitives.IsEmpty());

  nsTArray<nsIntRegion> resultChangeRegions;

  for (int32_t i = 0; i < int32_t(primitives.Length()); ++i) {
    const FilterPrimitiveDescription& descr = primitives[i];

    nsTArray<nsIntRegion> inputChangeRegions;
    for (size_t j = 0; j < descr.NumberOfInputs(); j++) {
      int32_t inputIndex = descr.InputPrimitiveIndex(j);
      MOZ_ASSERT(inputIndex < i, "bad input index");
      nsIntRegion inputChangeRegion =
        ElementForIndex(inputIndex, resultChangeRegions,
                        aSourceGraphicChange, aFillPaintChange,
                        aStrokePaintChange);
      inputChangeRegions.AppendElement(inputChangeRegion);
    }
    nsIntRegion changeRegion =
      ResultChangeRegionForPrimitive(descr, inputChangeRegions);
    changeRegion.And(changeRegion, descr.PrimitiveSubregion());
    resultChangeRegions.AppendElement(changeRegion);
  }

  MOZ_RELEASE_ASSERT(!resultChangeRegions.IsEmpty());
  return resultChangeRegions[resultChangeRegions.Length() - 1];
}

static float
ResultOfZeroUnderTransferFunction(const AttributeMap& aFunctionAttributes)
{
  switch (aFunctionAttributes.GetUint(eComponentTransferFunctionType)) {
    case SVG_FECOMPONENTTRANSFER_TYPE_TABLE:
    {
      const nsTArray<float>& tableValues =
        aFunctionAttributes.GetFloats(eComponentTransferFunctionTableValues);
      if (tableValues.Length() < 2) {
        return 0.0f;
      }
      return tableValues[0];
    }

    case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE:
    {
      const nsTArray<float>& tableValues =
        aFunctionAttributes.GetFloats(eComponentTransferFunctionTableValues);
      if (tableValues.Length() < 1) {
        return 0.0f;
      }
      return tableValues[0];
    }

    case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR:
      return aFunctionAttributes.GetFloat(eComponentTransferFunctionIntercept);

    case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA:
      return aFunctionAttributes.GetFloat(eComponentTransferFunctionOffset);

    case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
    default:
      return 0.0f;
  }
}

nsIntRegion
FilterSupport::PostFilterExtentsForPrimitive(const FilterPrimitiveDescription& aDescription,
                                             const nsTArray<nsIntRegion>& aInputExtents)
{
  const AttributeMap& atts = aDescription.Attributes();
  switch (aDescription.Type()) {

    case PrimitiveType::Empty:
      return IntRect();

    case PrimitiveType::Composite:
    {
      uint32_t op = atts.GetUint(eCompositeOperator);
      if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) {
        // The arithmetic composite primitive can draw outside the bounding
        // box of its source images.
        const nsTArray<float>& coefficients = atts.GetFloats(eCompositeCoefficients);
        MOZ_ASSERT(coefficients.Length() == 4);

        // The calculation is:
        // r = c[0] * in[0] * in[1] + c[1] * in[0] + c[2] * in[1] + c[3]
        nsIntRegion region;
        if (coefficients[0] > 0.0f) {
          region = aInputExtents[0].Intersect(aInputExtents[1]);
        }
        if (coefficients[1] > 0.0f) {
          region.Or(region, aInputExtents[0]);
        }
        if (coefficients[2] > 0.0f) {
          region.Or(region, aInputExtents[1]);
        }
        if (coefficients[3] > 0.0f) {
          region = aDescription.PrimitiveSubregion();
        }
        return region;
      }
      if (op == SVG_FECOMPOSITE_OPERATOR_IN) {
        return aInputExtents[0].Intersect(aInputExtents[1]);
      }
      return ResultChangeRegionForPrimitive(aDescription, aInputExtents);
    }

    case PrimitiveType::Flood:
    {
      if (atts.GetColor(eFloodColor).a == 0.0f) {
        return IntRect();
      }
      return aDescription.PrimitiveSubregion();
    }

    case PrimitiveType::ColorMatrix:
    {
      if (atts.GetUint(eColorMatrixType) == (uint32_t)SVG_FECOLORMATRIX_TYPE_MATRIX) {
        const nsTArray<float>& values = atts.GetFloats(eColorMatrixValues);
        if (values.Length() == 20 && values[19] > 0.0f) {
          return aDescription.PrimitiveSubregion();
        }
      }
      return aInputExtents[0];
    }

    case PrimitiveType::ComponentTransfer:
    {
      AttributeMap functionAttributes =
        atts.GetAttributeMap(eComponentTransferFunctionA);
      if (ResultOfZeroUnderTransferFunction(functionAttributes) > 0.0f) {
        return aDescription.PrimitiveSubregion();
      }
      return aInputExtents[0];
    }

    case PrimitiveType::Turbulence:
    case PrimitiveType::Image:
    case PrimitiveType::DiffuseLighting:
    case PrimitiveType::SpecularLighting:
    {
      return aDescription.PrimitiveSubregion();
    }

    case PrimitiveType::Morphology:
    {
      uint32_t op = atts.GetUint(eMorphologyOperator);
      if (op == SVG_OPERATOR_ERODE) {
        return aInputExtents[0];
      }
      Size radii = atts.GetSize(eMorphologyRadii);
      int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius);
      int32_t ry = clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius);
      return aInputExtents[0].Inflated(nsIntMargin(ry, rx, ry, rx));
    }

    default:
      return ResultChangeRegionForPrimitive(aDescription, aInputExtents);
  }
}

/* static */ nsIntRegion
FilterSupport::ComputePostFilterExtents(const FilterDescription& aFilter,
                                        const nsIntRegion& aSourceGraphicExtents)
{
  const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives;
  MOZ_RELEASE_ASSERT(!primitives.IsEmpty());
  nsTArray<nsIntRegion> postFilterExtents;

  for (int32_t i = 0; i < int32_t(primitives.Length()); ++i) {
    const FilterPrimitiveDescription& descr = primitives[i];
    nsIntRegion filterSpace = descr.FilterSpaceBounds();

    nsTArray<nsIntRegion> inputExtents;
    for (size_t j = 0; j < descr.NumberOfInputs(); j++) {
      int32_t inputIndex = descr.InputPrimitiveIndex(j);
      MOZ_ASSERT(inputIndex < i, "bad input index");
      nsIntRegion inputExtent =
        ElementForIndex(inputIndex, postFilterExtents,
                        aSourceGraphicExtents, filterSpace, filterSpace);
      inputExtents.AppendElement(inputExtent);
    }
    nsIntRegion extent = PostFilterExtentsForPrimitive(descr, inputExtents);
    extent.And(extent, descr.PrimitiveSubregion());
    postFilterExtents.AppendElement(extent);
  }

  MOZ_RELEASE_ASSERT(!postFilterExtents.IsEmpty());
  return postFilterExtents[postFilterExtents.Length() - 1];
}

static nsIntRegion
SourceNeededRegionForPrimitive(const FilterPrimitiveDescription& aDescription,
                               const nsIntRegion& aResultNeededRegion,
                               int32_t aInputIndex)
{
  const AttributeMap& atts = aDescription.Attributes();
  switch (aDescription.Type()) {

    case PrimitiveType::Flood:
    case PrimitiveType::Turbulence:
    case PrimitiveType::Image:
      MOZ_CRASH("GFX: this shouldn't be called for filters without inputs");
      return nsIntRegion();

    case PrimitiveType::Empty:
      return nsIntRegion();

    case PrimitiveType::Blend:
    case PrimitiveType::Composite:
    case PrimitiveType::Merge:
    case PrimitiveType::ColorMatrix:
    case PrimitiveType::ComponentTransfer:
    case PrimitiveType::ToAlpha:
      return aResultNeededRegion;

    case PrimitiveType::Morphology:
    {
      Size radii = atts.GetSize(eMorphologyRadii);
      int32_t rx = clamped(int32_t(ceil(radii.width)), 0, kMorphologyMaxRadius);
      int32_t ry = clamped(int32_t(ceil(radii.height)), 0, kMorphologyMaxRadius);
      return aResultNeededRegion.Inflated(nsIntMargin(ry, rx, ry, rx));
    }

    case PrimitiveType::Tile:
      return IntRect(INT32_MIN/2, INT32_MIN/2, INT32_MAX, INT32_MAX);

    case PrimitiveType::ConvolveMatrix:
    {
      Size kernelUnitLength = atts.GetSize(eConvolveMatrixKernelUnitLength);
      IntSize kernelSize = atts.GetIntSize(eConvolveMatrixKernelSize);
      IntPoint target = atts.GetIntPoint(eConvolveMatrixTarget);
      nsIntMargin m(ceil(kernelUnitLength.width * (kernelSize.width - target.x - 1)),
                    ceil(kernelUnitLength.height * (kernelSize.height - target.y - 1)),
                    ceil(kernelUnitLength.width * (target.x)),
                    ceil(kernelUnitLength.height * (target.y)));
      return aResultNeededRegion.Inflated(m);
    }

    case PrimitiveType::Offset:
    {
      IntPoint offset = atts.GetIntPoint(eOffsetOffset);
      return aResultNeededRegion.MovedBy(-nsIntPoint(offset.x, offset.y));
    }

    case PrimitiveType::DisplacementMap:
    {
      if (aInputIndex == 1) {
        return aResultNeededRegion;
      }
      int32_t scale = ceil(std::abs(atts.GetFloat(eDisplacementMapScale)));
      return aResultNeededRegion.Inflated(nsIntMargin(scale, scale, scale, scale));
    }

    case PrimitiveType::GaussianBlur:
    {
      Size stdDeviation = atts.GetSize(eGaussianBlurStdDeviation);
      int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width);
      int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height);
      return aResultNeededRegion.Inflated(nsIntMargin(dy, dx, dy, dx));
    }

    case PrimitiveType::DropShadow:
    {
      IntPoint offset = atts.GetIntPoint(eDropShadowOffset);
      nsIntRegion offsetRegion =
        aResultNeededRegion.MovedBy(-nsIntPoint(offset.x, offset.y));
      Size stdDeviation = atts.GetSize(eDropShadowStdDeviation);
      int32_t dx = InflateSizeForBlurStdDev(stdDeviation.width);
      int32_t dy = InflateSizeForBlurStdDev(stdDeviation.height);
      nsIntRegion blurRegion = offsetRegion.Inflated(nsIntMargin(dy, dx, dy, dx));
      blurRegion.Or(blurRegion, aResultNeededRegion);
      return blurRegion;
    }

    case PrimitiveType::DiffuseLighting:
    case PrimitiveType::SpecularLighting:
    {
      Size kernelUnitLength = atts.GetSize(eLightingKernelUnitLength);
      int32_t dx = ceil(kernelUnitLength.width);
      int32_t dy = ceil(kernelUnitLength.height);
      return aResultNeededRegion.Inflated(nsIntMargin(dy, dx, dy, dx));
    }

    default:
      return nsIntRegion();
  }

}

/* static */ void
FilterSupport::ComputeSourceNeededRegions(const FilterDescription& aFilter,
                                          const nsIntRegion& aResultNeededRegion,
                                          nsIntRegion& aSourceGraphicNeededRegion,
                                          nsIntRegion& aFillPaintNeededRegion,
                                          nsIntRegion& aStrokePaintNeededRegion)
{
  const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives;
  MOZ_ASSERT(!primitives.IsEmpty());
  if (primitives.IsEmpty()) {
    return;
  }

  nsTArray<nsIntRegion> primitiveNeededRegions;
  primitiveNeededRegions.AppendElements(primitives.Length());

  primitiveNeededRegions[primitives.Length() - 1] = aResultNeededRegion;

  for (int32_t i = primitives.Length() - 1; i >= 0; --i) {
    const FilterPrimitiveDescription& descr = primitives[i];
    nsIntRegion neededRegion = primitiveNeededRegions[i];
    neededRegion.And(neededRegion, descr.PrimitiveSubregion());

    for (size_t j = 0; j < descr.NumberOfInputs(); j++) {
      int32_t inputIndex = descr.InputPrimitiveIndex(j);
      MOZ_ASSERT(inputIndex < i, "bad input index");
      nsIntRegion* inputNeededRegion = const_cast<nsIntRegion*>(
        &ElementForIndex(inputIndex, primitiveNeededRegions,
                         aSourceGraphicNeededRegion,
                         aFillPaintNeededRegion, aStrokePaintNeededRegion));
      inputNeededRegion->Or(*inputNeededRegion,
        SourceNeededRegionForPrimitive(descr, neededRegion, j));
    }
  }

  // Clip original SourceGraphic to first filter region.
  const FilterPrimitiveDescription& firstDescr = primitives[0];
  aSourceGraphicNeededRegion.And(aSourceGraphicNeededRegion,
                                 firstDescr.FilterSpaceBounds());
}

// FilterPrimitiveDescription

FilterPrimitiveDescription::FilterPrimitiveDescription()
 : mType(PrimitiveType::Empty)
 , mOutputColorSpace(ColorSpace::SRGB)
 , mIsTainted(false)
{
}

FilterPrimitiveDescription::FilterPrimitiveDescription(PrimitiveType aType)
 : mType(aType)
 , mOutputColorSpace(ColorSpace::SRGB)
 , mIsTainted(false)
{
}

FilterPrimitiveDescription::FilterPrimitiveDescription(const FilterPrimitiveDescription& aOther)
 : mType(aOther.mType)
 , mAttributes(aOther.mAttributes)
 , mInputPrimitives(aOther.mInputPrimitives)
 , mFilterPrimitiveSubregion(aOther.mFilterPrimitiveSubregion)
 , mFilterSpaceBounds(aOther.mFilterSpaceBounds)
 , mInputColorSpaces(aOther.mInputColorSpaces)
 , mOutputColorSpace(aOther.mOutputColorSpace)
 , mIsTainted(aOther.mIsTainted)
{
}

FilterPrimitiveDescription&
FilterPrimitiveDescription::operator=(const FilterPrimitiveDescription& aOther)
{
  if (this != &aOther) {
    mType = aOther.mType;
    mAttributes = aOther.mAttributes;
    mInputPrimitives = aOther.mInputPrimitives;
    mFilterPrimitiveSubregion = aOther.mFilterPrimitiveSubregion;
    mFilterSpaceBounds = aOther.mFilterSpaceBounds;
    mInputColorSpaces = aOther.mInputColorSpaces;
    mOutputColorSpace = aOther.mOutputColorSpace;
    mIsTainted = aOther.mIsTainted;
  }
  return *this;
}

bool
FilterPrimitiveDescription::operator==(const FilterPrimitiveDescription& aOther) const
{
  return mType == aOther.mType &&
    mFilterPrimitiveSubregion.IsEqualInterior(aOther.mFilterPrimitiveSubregion) &&
    mFilterSpaceBounds.IsEqualInterior(aOther.mFilterSpaceBounds) &&
    mOutputColorSpace == aOther.mOutputColorSpace &&
    mIsTainted == aOther.mIsTainted &&
    mInputPrimitives == aOther.mInputPrimitives &&
    mInputColorSpaces == aOther.mInputColorSpaces &&
    mAttributes == aOther.mAttributes;
}

// FilterDescription

bool
FilterDescription::operator==(const FilterDescription& aOther) const
{
  return mPrimitives == aOther.mPrimitives;
}

// AttributeMap

// A class that wraps different types for easy storage in a hashtable. Only
// used by AttributeMap.
struct FilterAttribute {
  FilterAttribute(const FilterAttribute& aOther);
  ~FilterAttribute();

  bool operator==(const FilterAttribute& aOther) const;
  bool operator!=(const FilterAttribute& aOther) const
  {
    return !(*this == aOther);
  }

  AttributeType Type() const { return mType; }

#define MAKE_CONSTRUCTOR_AND_ACCESSOR_BASIC(type, typeLabel)   \
  explicit FilterAttribute(type aValue)                        \
   : mType(AttributeType::e##typeLabel), m##typeLabel(aValue)  \
  {}                                                           \
  type As##typeLabel() {                                       \
    MOZ_ASSERT(mType == AttributeType::e##typeLabel);          \
    return m##typeLabel;                                       \
  }

#define MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(className)         \
  explicit FilterAttribute(const className& aValue)            \
   : mType(AttributeType::e##className), m##className(new className(aValue)) \
  {}                                                           \
  className As##className() {                                  \
    MOZ_ASSERT(mType == AttributeType::e##className);          \
    return *m##className;                                      \
  }

  MAKE_CONSTRUCTOR_AND_ACCESSOR_BASIC(bool, Bool)
  MAKE_CONSTRUCTOR_AND_ACCESSOR_BASIC(uint32_t, Uint)
  MAKE_CONSTRUCTOR_AND_ACCESSOR_BASIC(float, Float)
  MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(Size)
  MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(IntSize)
  MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(IntPoint)
  MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(Matrix)
  MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(Matrix5x4)
  MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(Point3D)
  MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(Color)
  MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS(AttributeMap)

#undef MAKE_CONSTRUCTOR_AND_ACCESSOR_BASIC
#undef MAKE_CONSTRUCTOR_AND_ACCESSOR_CLASS

  FilterAttribute(const float* aValue, uint32_t aLength)
   : mType(AttributeType::eFloats)
  {
    mFloats = new nsTArray<float>();
    mFloats->AppendElements(aValue, aLength);
  }

  const nsTArray<float>& AsFloats() const {
    MOZ_ASSERT(mType == AttributeType::eFloats);
    return *mFloats;
  }

private:
  const AttributeType mType;

  union {
    bool mBool;
    uint32_t mUint;
    float mFloat;
    Size* mSize;
    IntSize* mIntSize;
    IntPoint* mIntPoint;
    Matrix* mMatrix;
    Matrix5x4* mMatrix5x4;
    Point3D* mPoint3D;
    Color* mColor;
    AttributeMap* mAttributeMap;
    nsTArray<float>* mFloats;
  };
};

FilterAttribute::FilterAttribute(const FilterAttribute& aOther)
 : mType(aOther.mType)
{
  switch (mType) {
    case AttributeType::eBool:
      mBool = aOther.mBool;
      break;
    case AttributeType::eUint:
      mUint = aOther.mUint;
      break;
    case AttributeType::eFloat:
      mFloat = aOther.mFloat;
      break;

#define HANDLE_CLASS(className)                            \
    case AttributeType::e##className:                      \
      m##className = new className(*aOther.m##className);  \
      break;

    HANDLE_CLASS(Size)
    HANDLE_CLASS(IntSize)
    HANDLE_CLASS(IntPoint)
    HANDLE_CLASS(Matrix)
    HANDLE_CLASS(Matrix5x4)
    HANDLE_CLASS(Point3D)
    HANDLE_CLASS(Color)
    HANDLE_CLASS(AttributeMap)

#undef HANDLE_CLASS

    case AttributeType::eFloats:
      mFloats = new nsTArray<float>(*aOther.mFloats);
      break;
    case AttributeType::Max:
      break;
  }
}

FilterAttribute::~FilterAttribute() {
  switch (mType) {
    case AttributeType::Max:
    case AttributeType::eBool:
    case AttributeType::eUint:
    case AttributeType::eFloat:
      break;

#define HANDLE_CLASS(className)                            \
    case AttributeType::e##className:                      \
      delete m##className;                                 \
      break;

    HANDLE_CLASS(Size)
    HANDLE_CLASS(IntSize)
    HANDLE_CLASS(IntPoint)
    HANDLE_CLASS(Matrix)
    HANDLE_CLASS(Matrix5x4)
    HANDLE_CLASS(Point3D)
    HANDLE_CLASS(Color)
    HANDLE_CLASS(AttributeMap)

#undef HANDLE_CLASS

    case AttributeType::eFloats:
      delete mFloats;
      break;
  }
}

bool
FilterAttribute::operator==(const FilterAttribute& aOther) const
{
  if (mType != aOther.mType) {
    return false;
  }

  switch (mType) {

#define HANDLE_TYPE(typeName)                              \
    case AttributeType::e##typeName:                       \
      return m##typeName == aOther.m##typeName;

    HANDLE_TYPE(Bool)
    HANDLE_TYPE(Uint)
    HANDLE_TYPE(Float)
    HANDLE_TYPE(Size)
    HANDLE_TYPE(IntSize)
    HANDLE_TYPE(IntPoint)
    HANDLE_TYPE(Matrix)
    HANDLE_TYPE(Matrix5x4)
    HANDLE_TYPE(Point3D)
    HANDLE_TYPE(Color)
    HANDLE_TYPE(AttributeMap)
    HANDLE_TYPE(Floats)

#undef HANDLE_TYPE

    default:
      return false;
  }
}

typedef FilterAttribute Attribute;

AttributeMap::AttributeMap()
{
}

AttributeMap::~AttributeMap()
{
}

AttributeMap::AttributeMap(const AttributeMap& aOther)
{
  for (auto iter = aOther.mMap.Iter(); !iter.Done(); iter.Next()) {
    const uint32_t& attributeName = iter.Key();
    Attribute* attribute = iter.UserData();
    mMap.Put(attributeName, new Attribute(*attribute));
  }
}

AttributeMap&
AttributeMap::operator=(const AttributeMap& aOther)
{
  if (this != &aOther) {
    mMap.Clear();
    for (auto iter = aOther.mMap.Iter(); !iter.Done(); iter.Next()) {
      const uint32_t& attributeName = iter.Key();
      Attribute* attribute = iter.UserData();
      mMap.Put(attributeName, new Attribute(*attribute));
    }
  }
  return *this;
}

bool
AttributeMap::operator==(const AttributeMap& aOther) const
{
  if (mMap.Count() != aOther.mMap.Count()) {
    return false;
  }

  for (auto iter = aOther.mMap.Iter(); !iter.Done(); iter.Next()) {
    const uint32_t& attributeName = iter.Key();
    Attribute* attribute = iter.UserData();
    Attribute* matchingAttribute = mMap.Get(attributeName);
    if (!matchingAttribute || *matchingAttribute != *attribute) {
      return false;
    }
  }

  return true;
}

uint32_t
AttributeMap::Count() const
{
  return mMap.Count();
}

nsClassHashtable<nsUint32HashKey, FilterAttribute>::Iterator
AttributeMap::ConstIter() const
{
  return mMap.ConstIter();
}

/* static */ AttributeType
AttributeMap::GetType(FilterAttribute* aAttribute)
{
  return aAttribute->Type();
}

#define MAKE_ATTRIBUTE_HANDLERS_BASIC(type, typeLabel, defaultValue) \
  type                                                               \
  AttributeMap::Get##typeLabel(AttributeName aName) const {          \
    Attribute* value = mMap.Get(aName);                              \
    return value ? value->As##typeLabel() : defaultValue;            \
  }                                                                  \
  void                                                               \
  AttributeMap::Set(AttributeName aName, type aValue) {              \
    mMap.Put(aName, new Attribute(aValue));                          \
  }

#define MAKE_ATTRIBUTE_HANDLERS_CLASS(className)                     \
  className                                                          \
  AttributeMap::Get##className(AttributeName aName) const {          \
    Attribute* value = mMap.Get(aName);                              \
    return value ? value->As##className() : className();             \
  }                                                                  \
  void                                                               \
  AttributeMap::Set(AttributeName aName, const className& aValue) {  \
    mMap.Put(aName, new Attribute(aValue));                          \
  }

MAKE_ATTRIBUTE_HANDLERS_BASIC(bool, Bool, false)
MAKE_ATTRIBUTE_HANDLERS_BASIC(uint32_t, Uint, 0)
MAKE_ATTRIBUTE_HANDLERS_BASIC(float, Float, 0)
MAKE_ATTRIBUTE_HANDLERS_CLASS(Size)
MAKE_ATTRIBUTE_HANDLERS_CLASS(IntSize)
MAKE_ATTRIBUTE_HANDLERS_CLASS(IntPoint)
MAKE_ATTRIBUTE_HANDLERS_CLASS(Matrix)
MAKE_ATTRIBUTE_HANDLERS_CLASS(Matrix5x4)
MAKE_ATTRIBUTE_HANDLERS_CLASS(Point3D)
MAKE_ATTRIBUTE_HANDLERS_CLASS(Color)
MAKE_ATTRIBUTE_HANDLERS_CLASS(AttributeMap)

#undef MAKE_ATTRIBUTE_HANDLERS_BASIC
#undef MAKE_ATTRIBUTE_HANDLERS_CLASS

const nsTArray<float>&
AttributeMap::GetFloats(AttributeName aName) const
{
  Attribute* value = mMap.LookupForAdd(aName).OrInsert(
    [] () { return new Attribute(nullptr, 0); });
  return value->AsFloats();
}

void
AttributeMap::Set(AttributeName aName, const float* aValues, int32_t aLength)
{
  mMap.Put(aName, new Attribute(aValues, aLength));
}

} // namespace gfx
} // namespace mozilla