Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include <cmath>
#include "DataSurfaceHelpers.h"
#include "FilterNodeSoftware.h"
#include "2D.h"
#include "Tools.h"
#include "Blur.h"
#include <map>
#include "FilterProcessing.h"
#include "Logging.h"
#include "mozilla/PodOperations.h"
#include "mozilla/DebugOnly.h"
// #define DEBUG_DUMP_SURFACES
#ifdef DEBUG_DUMP_SURFACES
# include "gfxUtils.h" // not part of Moz2D
#endif
namespace mozilla {
namespace gfx {
namespace {
/**
* This class provides a way to get a pow() results in constant-time. It works
* by caching 129 ((1 << sCacheIndexPrecisionBits) + 1) values for bases between
* 0 and 1 and a fixed exponent.
**/
class PowCache {
public:
PowCache() : mNumPowTablePreSquares(-1) {}
void CacheForExponent(Float aExponent) {
// Since we are in the world where we only care about
// input and results in [0,1], there is no point in
// dealing with non-positive exponents.
if (aExponent <= 0) {
mNumPowTablePreSquares = -1;
return;
}
int numPreSquares = 0;
while (numPreSquares < 5 && aExponent > (1 << (numPreSquares + 2))) {
numPreSquares++;
}
mNumPowTablePreSquares = numPreSquares;
for (size_t i = 0; i < sCacheSize; i++) {
// sCacheSize is chosen in such a way that a takes values
// from 0.0 to 1.0 inclusive.
Float a = i / Float(1 << sCacheIndexPrecisionBits);
MOZ_ASSERT(0.0f <= a && a <= 1.0f,
"We only want to cache for bases between 0 and 1.");
for (int j = 0; j < mNumPowTablePreSquares; j++) {
a = sqrt(a);
}
uint32_t cachedInt = pow(a, aExponent) * (1 << sOutputIntPrecisionBits);
MOZ_ASSERT(cachedInt < (1 << (sizeof(mPowTable[i]) * 8)),
"mPowCache integer type too small");
mPowTable[i] = cachedInt;
}
}
// Only call Pow() if HasPowerTable() would return true, to avoid complicating
// this code and having it just return (1 << sOutputIntPrecisionBits))
uint16_t Pow(uint16_t aBase) {
MOZ_ASSERT(HasPowerTable());
// Results should be similar to what the following code would produce:
// Float x = Float(aBase) / (1 << sInputIntPrecisionBits);
// return uint16_t(pow(x, aExponent) * (1 << sOutputIntPrecisionBits));
MOZ_ASSERT(aBase <= (1 << sInputIntPrecisionBits),
"aBase needs to be between 0 and 1!");
uint32_t a = aBase;
for (int j = 0; j < mNumPowTablePreSquares; j++) {
a = a * a >> sInputIntPrecisionBits;
}
uint32_t i = a >> (sInputIntPrecisionBits - sCacheIndexPrecisionBits);
MOZ_ASSERT(i < sCacheSize, "out-of-bounds mPowTable access");
return mPowTable[i];
}
static const int sInputIntPrecisionBits = 15;
static const int sOutputIntPrecisionBits = 15;
static const int sCacheIndexPrecisionBits = 7;
inline bool HasPowerTable() const { return mNumPowTablePreSquares >= 0; }
private:
static const size_t sCacheSize = (1 << sCacheIndexPrecisionBits) + 1;
int mNumPowTablePreSquares;
uint16_t mPowTable[sCacheSize];
};
class PointLightSoftware {
public:
bool SetAttribute(uint32_t aIndex, Float) { return false; }
bool SetAttribute(uint32_t aIndex, const Point3D&);
void Prepare() {}
Point3D GetVectorToLight(const Point3D& aTargetPoint);
uint32_t GetColor(uint32_t aLightColor, const Point3D& aVectorToLight);
private:
Point3D mPosition;
};
class SpotLightSoftware {
public:
SpotLightSoftware();
bool SetAttribute(uint32_t aIndex, Float);
bool SetAttribute(uint32_t aIndex, const Point3D&);
void Prepare();
Point3D GetVectorToLight(const Point3D& aTargetPoint);
uint32_t GetColor(uint32_t aLightColor, const Point3D& aVectorToLight);
private:
Point3D mPosition;
Point3D mPointsAt;
Point3D mVectorFromFocusPointToLight;
Float mSpecularFocus;
Float mLimitingConeAngle;
Float mLimitingConeCos;
PowCache mPowCache;
};
class DistantLightSoftware {
public:
DistantLightSoftware();
bool SetAttribute(uint32_t aIndex, Float);
bool SetAttribute(uint32_t aIndex, const Point3D&) { return false; }
void Prepare();
Point3D GetVectorToLight(const Point3D& aTargetPoint);
uint32_t GetColor(uint32_t aLightColor, const Point3D& aVectorToLight);
private:
Float mAzimuth;
Float mElevation;
Point3D mVectorToLight;
};
class DiffuseLightingSoftware {
public:
DiffuseLightingSoftware();
bool SetAttribute(uint32_t aIndex, Float);
void Prepare() {}
uint32_t LightPixel(const Point3D& aNormal, const Point3D& aVectorToLight,
uint32_t aColor);
private:
Float mDiffuseConstant;
};
class SpecularLightingSoftware {
public:
SpecularLightingSoftware();
bool SetAttribute(uint32_t aIndex, Float);
void Prepare();
uint32_t LightPixel(const Point3D& aNormal, const Point3D& aVectorToLight,
uint32_t aColor);
private:
Float mSpecularConstant;
Float mSpecularExponent;
uint32_t mSpecularConstantInt;
PowCache mPowCache;
};
} // unnamed namespace
// from xpcom/ds/nsMathUtils.h
static int32_t NS_lround(double x) {
return x >= 0.0 ? int32_t(x + 0.5) : int32_t(x - 0.5);
}
static already_AddRefed<DataSourceSurface> CloneAligned(
DataSourceSurface* aSource) {
return CreateDataSourceSurfaceByCloning(aSource);
}
static void FillRectWithPixel(DataSourceSurface* aSurface,
const IntRect& aFillRect, IntPoint aPixelPos) {
MOZ_ASSERT(!aFillRect.Overflows());
MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFillRect),
"aFillRect needs to be completely inside the surface");
MOZ_ASSERT(SurfaceContainsPoint(aSurface, aPixelPos),
"aPixelPos needs to be inside the surface");
DataSourceSurface::ScopedMap surfMap(aSurface, DataSourceSurface::READ_WRITE);
if (MOZ2D_WARN_IF(!surfMap.IsMapped())) {
return;
}
uint8_t* sourcePixelData =
DataAtOffset(aSurface, surfMap.GetMappedSurface(), aPixelPos);
uint8_t* data =
DataAtOffset(aSurface, surfMap.GetMappedSurface(), aFillRect.TopLeft());
int bpp = BytesPerPixel(aSurface->GetFormat());
// Fill the first row by hand.
if (bpp == 4) {
uint32_t sourcePixel = *(uint32_t*)sourcePixelData;
for (int32_t x = 0; x < aFillRect.Width(); x++) {
*((uint32_t*)data + x) = sourcePixel;
}
} else if (BytesPerPixel(aSurface->GetFormat()) == 1) {
uint8_t sourcePixel = *sourcePixelData;
memset(data, sourcePixel, aFillRect.Width());
}
// Copy the first row into the other rows.
for (int32_t y = 1; y < aFillRect.Height(); y++) {
PodCopy(data + y * surfMap.GetStride(), data, aFillRect.Width() * bpp);
}
}
static void FillRectWithVerticallyRepeatingHorizontalStrip(
DataSourceSurface* aSurface, const IntRect& aFillRect,
const IntRect& aSampleRect) {
MOZ_ASSERT(!aFillRect.Overflows());
MOZ_ASSERT(!aSampleRect.Overflows());
MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFillRect),
"aFillRect needs to be completely inside the surface");
MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aSampleRect),
"aSampleRect needs to be completely inside the surface");
DataSourceSurface::ScopedMap surfMap(aSurface, DataSourceSurface::READ_WRITE);
if (MOZ2D_WARN_IF(!surfMap.IsMapped())) {
return;
}
uint8_t* sampleData =
DataAtOffset(aSurface, surfMap.GetMappedSurface(), aSampleRect.TopLeft());
uint8_t* data =
DataAtOffset(aSurface, surfMap.GetMappedSurface(), aFillRect.TopLeft());
if (BytesPerPixel(aSurface->GetFormat()) == 4) {
for (int32_t y = 0; y < aFillRect.Height(); y++) {
PodCopy((uint32_t*)data, (uint32_t*)sampleData, aFillRect.Width());
data += surfMap.GetStride();
}
} else if (BytesPerPixel(aSurface->GetFormat()) == 1) {
for (int32_t y = 0; y < aFillRect.Height(); y++) {
PodCopy(data, sampleData, aFillRect.Width());
data += surfMap.GetStride();
}
}
}
static void FillRectWithHorizontallyRepeatingVerticalStrip(
DataSourceSurface* aSurface, const IntRect& aFillRect,
const IntRect& aSampleRect) {
MOZ_ASSERT(!aFillRect.Overflows());
MOZ_ASSERT(!aSampleRect.Overflows());
MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFillRect),
"aFillRect needs to be completely inside the surface");
MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aSampleRect),
"aSampleRect needs to be completely inside the surface");
DataSourceSurface::ScopedMap surfMap(aSurface, DataSourceSurface::READ_WRITE);
if (MOZ2D_WARN_IF(!surfMap.IsMapped())) {
return;
}
uint8_t* sampleData =
DataAtOffset(aSurface, surfMap.GetMappedSurface(), aSampleRect.TopLeft());
uint8_t* data =
DataAtOffset(aSurface, surfMap.GetMappedSurface(), aFillRect.TopLeft());
if (BytesPerPixel(aSurface->GetFormat()) == 4) {
for (int32_t y = 0; y < aFillRect.Height(); y++) {
int32_t sampleColor = *((uint32_t*)sampleData);
for (int32_t x = 0; x < aFillRect.Width(); x++) {
*((uint32_t*)data + x) = sampleColor;
}
data += surfMap.GetStride();
sampleData += surfMap.GetStride();
}
} else if (BytesPerPixel(aSurface->GetFormat()) == 1) {
for (int32_t y = 0; y < aFillRect.Height(); y++) {
uint8_t sampleColor = *sampleData;
memset(data, sampleColor, aFillRect.Width());
data += surfMap.GetStride();
sampleData += surfMap.GetStride();
}
}
}
static void DuplicateEdges(DataSourceSurface* aSurface,
const IntRect& aFromRect) {
MOZ_ASSERT(!aFromRect.Overflows());
MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFromRect),
"aFromRect needs to be completely inside the surface");
IntSize size = aSurface->GetSize();
IntRect fill;
IntRect sampleRect;
for (int32_t ix = 0; ix < 3; ix++) {
switch (ix) {
case 0:
fill.SetRectX(0, aFromRect.X());
sampleRect.SetRectX(fill.XMost(), 1);
break;
case 1:
fill.SetRectX(aFromRect.X(), aFromRect.Width());
sampleRect.SetRectX(fill.X(), fill.Width());
break;
case 2:
fill.MoveToX(aFromRect.XMost());
fill.SetRightEdge(size.width);
sampleRect.SetRectX(fill.X() - 1, 1);
break;
}
if (fill.Width() <= 0) {
continue;
}
bool xIsMiddle = (ix == 1);
for (int32_t iy = 0; iy < 3; iy++) {
switch (iy) {
case 0:
fill.SetRectY(0, aFromRect.Y());
sampleRect.SetRectY(fill.YMost(), 1);
break;
case 1:
fill.SetRectY(aFromRect.Y(), aFromRect.Height());
sampleRect.SetRectY(fill.Y(), fill.Height());
break;
case 2:
fill.MoveToY(aFromRect.YMost());
fill.SetBottomEdge(size.height);
sampleRect.SetRectY(fill.Y() - 1, 1);
break;
}
if (fill.Height() <= 0) {
continue;
}
bool yIsMiddle = (iy == 1);
if (!xIsMiddle && !yIsMiddle) {
// Corner
FillRectWithPixel(aSurface, fill, sampleRect.TopLeft());
}
if (xIsMiddle && !yIsMiddle) {
// Top middle or bottom middle
FillRectWithVerticallyRepeatingHorizontalStrip(aSurface, fill,
sampleRect);
}
if (!xIsMiddle && yIsMiddle) {
// Left middle or right middle
FillRectWithHorizontallyRepeatingVerticalStrip(aSurface, fill,
sampleRect);
}
}
}
}
static IntPoint TileIndex(const IntRect& aFirstTileRect,
const IntPoint& aPoint) {
return IntPoint(int32_t(floor(double(aPoint.x - aFirstTileRect.X()) /
aFirstTileRect.Width())),
int32_t(floor(double(aPoint.y - aFirstTileRect.Y()) /
aFirstTileRect.Height())));
}
static void TileSurface(DataSourceSurface* aSource, DataSourceSurface* aTarget,
const IntPoint& aOffset) {
IntRect sourceRect(aOffset, aSource->GetSize());
IntRect targetRect(IntPoint(0, 0), aTarget->GetSize());
IntPoint startIndex = TileIndex(sourceRect, targetRect.TopLeft());
IntPoint endIndex = TileIndex(sourceRect, targetRect.BottomRight());
for (int32_t ix = startIndex.x; ix <= endIndex.x; ix++) {
for (int32_t iy = startIndex.y; iy <= endIndex.y; iy++) {
IntPoint destPoint(sourceRect.X() + ix * sourceRect.Width(),
sourceRect.Y() + iy * sourceRect.Height());
IntRect destRect(destPoint, sourceRect.Size());
destRect = destRect.Intersect(targetRect);
IntRect srcRect = destRect - destPoint;
CopyRect(aSource, aTarget, srcRect, destRect.TopLeft());
}
}
}
static already_AddRefed<DataSourceSurface> GetDataSurfaceInRect(
SourceSurface* aSurface, const IntRect& aSurfaceRect,
const IntRect& aDestRect, ConvolveMatrixEdgeMode aEdgeMode) {
MOZ_ASSERT(aSurface ? aSurfaceRect.Size() == aSurface->GetSize()
: aSurfaceRect.IsEmpty());
if (aSurfaceRect.Overflows() || aDestRect.Overflows()) {
// We can't rely on the intersection calculations below to make sense when
// XMost() or YMost() overflow. Bail out.
return nullptr;
}
IntRect sourceRect = aSurfaceRect;
if (sourceRect.IsEqualEdges(aDestRect)) {
return aSurface ? aSurface->GetDataSurface() : nullptr;
}
IntRect intersect = sourceRect.Intersect(aDestRect);
// create rects that are in surface local space.
IntRect intersectInSourceSpace = intersect - sourceRect.TopLeft();
IntRect intersectInDestSpace = intersect - aDestRect.TopLeft();
SurfaceFormat format =
aSurface ? aSurface->GetFormat() : SurfaceFormat(SurfaceFormat::B8G8R8A8);
RefPtr<DataSourceSurface> target =
Factory::CreateDataSourceSurface(aDestRect.Size(), format, true);
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
if (!aSurface) {
return target.forget();
}
RefPtr<DataSourceSurface> dataSource = aSurface->GetDataSurface();
MOZ_ASSERT(dataSource);
if (aEdgeMode == EDGE_MODE_WRAP) {
TileSurface(dataSource, target, intersectInDestSpace.TopLeft());
return target.forget();
}
CopyRect(dataSource, target, intersectInSourceSpace,
intersectInDestSpace.TopLeft());
if (aEdgeMode == EDGE_MODE_DUPLICATE) {
DuplicateEdges(target, intersectInDestSpace);
}
return target.forget();
}
/* static */
already_AddRefed<FilterNode> FilterNodeSoftware::Create(FilterType aType) {
RefPtr<FilterNodeSoftware> filter;
switch (aType) {
case FilterType::BLEND:
filter = new FilterNodeBlendSoftware();
break;
case FilterType::TRANSFORM:
filter = new FilterNodeTransformSoftware();
break;
case FilterType::MORPHOLOGY:
filter = new FilterNodeMorphologySoftware();
break;
case FilterType::COLOR_MATRIX:
filter = new FilterNodeColorMatrixSoftware();
break;
case FilterType::FLOOD:
filter = new FilterNodeFloodSoftware();
break;
case FilterType::TILE:
filter = new FilterNodeTileSoftware();
break;
case FilterType::TABLE_TRANSFER:
filter = new FilterNodeTableTransferSoftware();
break;
case FilterType::DISCRETE_TRANSFER:
filter = new FilterNodeDiscreteTransferSoftware();
break;
case FilterType::LINEAR_TRANSFER:
filter = new FilterNodeLinearTransferSoftware();
break;
case FilterType::GAMMA_TRANSFER:
filter = new FilterNodeGammaTransferSoftware();
break;
case FilterType::CONVOLVE_MATRIX:
filter = new FilterNodeConvolveMatrixSoftware();
break;
case FilterType::DISPLACEMENT_MAP:
filter = new FilterNodeDisplacementMapSoftware();
break;
case FilterType::TURBULENCE:
filter = new FilterNodeTurbulenceSoftware();
break;
case FilterType::ARITHMETIC_COMBINE:
filter = new FilterNodeArithmeticCombineSoftware();
break;
case FilterType::COMPOSITE:
filter = new FilterNodeCompositeSoftware();
break;
case FilterType::GAUSSIAN_BLUR:
filter = new FilterNodeGaussianBlurSoftware();
break;
case FilterType::DIRECTIONAL_BLUR:
filter = new FilterNodeDirectionalBlurSoftware();
break;
case FilterType::CROP:
filter = new FilterNodeCropSoftware();
break;
case FilterType::PREMULTIPLY:
filter = new FilterNodePremultiplySoftware();
break;
case FilterType::UNPREMULTIPLY:
filter = new FilterNodeUnpremultiplySoftware();
break;
case FilterType::OPACITY:
filter = new FilterNodeOpacitySoftware();
break;
case FilterType::POINT_DIFFUSE:
filter = new FilterNodeLightingSoftware<PointLightSoftware,
DiffuseLightingSoftware>(
"FilterNodeLightingSoftware<PointLight, DiffuseLighting>");
break;
case FilterType::POINT_SPECULAR:
filter = new FilterNodeLightingSoftware<PointLightSoftware,
SpecularLightingSoftware>(
"FilterNodeLightingSoftware<PointLight, SpecularLighting>");
break;
case FilterType::SPOT_DIFFUSE:
filter = new FilterNodeLightingSoftware<SpotLightSoftware,
DiffuseLightingSoftware>(
"FilterNodeLightingSoftware<SpotLight, DiffuseLighting>");
break;
case FilterType::SPOT_SPECULAR:
filter = new FilterNodeLightingSoftware<SpotLightSoftware,
SpecularLightingSoftware>(
"FilterNodeLightingSoftware<SpotLight, SpecularLighting>");
break;
case FilterType::DISTANT_DIFFUSE:
filter = new FilterNodeLightingSoftware<DistantLightSoftware,
DiffuseLightingSoftware>(
"FilterNodeLightingSoftware<DistantLight, DiffuseLighting>");
break;
case FilterType::DISTANT_SPECULAR:
filter = new FilterNodeLightingSoftware<DistantLightSoftware,
SpecularLightingSoftware>(
"FilterNodeLightingSoftware<DistantLight, SpecularLighting>");
break;
}
return filter.forget();
}
void FilterNodeSoftware::Draw(DrawTarget* aDrawTarget, const Rect& aSourceRect,
const Point& aDestPoint,
const DrawOptions& aOptions) {
#ifdef DEBUG_DUMP_SURFACES
printf("<style>section{margin:10px;}</style><pre>\nRendering filter %s...\n",
GetName());
#endif
Rect renderRect = aSourceRect;
renderRect.RoundOut();
IntRect renderIntRect;
if (!renderRect.ToIntRect(&renderIntRect)) {
#ifdef DEBUG_DUMP_SURFACES
printf("render rect overflowed, not painting anything\n");
printf("</pre>\n");
#endif
return;
}
IntRect outputRect = GetOutputRectInRect(renderIntRect);
if (outputRect.Overflows()) {
#ifdef DEBUG_DUMP_SURFACES
printf("output rect overflowed, not painting anything\n");
printf("</pre>\n");
#endif
return;
}
RefPtr<DataSourceSurface> result;
if (!outputRect.IsEmpty()) {
result = GetOutput(outputRect);
}
if (!result) {
// Null results are allowed and treated as transparent. Don't draw anything.
#ifdef DEBUG_DUMP_SURFACES
printf("output returned null\n");
printf("</pre>\n");
#endif
return;
}
#ifdef DEBUG_DUMP_SURFACES
printf("output from %s:\n", GetName());
printf("<img src='");
gfxUtils::DumpAsDataURL(result);
printf("'>\n");
printf("</pre>\n");
#endif
Point sourceToDestOffset = aDestPoint - aSourceRect.TopLeft();
Rect renderedSourceRect = Rect(outputRect).Intersect(aSourceRect);
Rect renderedDestRect = renderedSourceRect + sourceToDestOffset;
if (result->GetFormat() == SurfaceFormat::A8) {
// Interpret the result as having implicitly black color channels.
aDrawTarget->PushClipRect(renderedDestRect);
aDrawTarget->MaskSurface(
ColorPattern(DeviceColor::MaskOpaqueBlack()), result,
Point(outputRect.TopLeft()) + sourceToDestOffset, aOptions);
aDrawTarget->PopClip();
} else {
aDrawTarget->DrawSurface(result, renderedDestRect,
renderedSourceRect - Point(outputRect.TopLeft()),
DrawSurfaceOptions(), aOptions);
}
}
already_AddRefed<DataSourceSurface> FilterNodeSoftware::GetOutput(
const IntRect& aRect) {
MOZ_ASSERT(GetOutputRectInRect(aRect).Contains(aRect));
if (aRect.Overflows()) {
return nullptr;
}
IntRect cachedRect;
IntRect requestedRect;
RefPtr<DataSourceSurface> cachedOutput;
// Retrieve a cached surface if we have one and it can
// satisfy this request, or else request a rect we will compute and cache
if (!mCachedRect.Contains(aRect)) {
RequestRect(aRect);
requestedRect = mRequestedRect;
} else {
MOZ_ASSERT(mCachedOutput, "cached rect but no cached output?");
cachedRect = mCachedRect;
cachedOutput = mCachedOutput;
}
if (!cachedOutput) {
// Compute the output
cachedOutput = Render(requestedRect);
// Update the cache for future requests
mCachedOutput = cachedOutput;
if (!mCachedOutput) {
mCachedRect = IntRect();
mRequestedRect = IntRect();
return nullptr;
}
mCachedRect = requestedRect;
mRequestedRect = IntRect();
cachedRect = mCachedRect;
}
return GetDataSurfaceInRect(cachedOutput, cachedRect, aRect, EDGE_MODE_NONE);
}
void FilterNodeSoftware::RequestRect(const IntRect& aRect) {
if (mRequestedRect.Contains(aRect)) {
// Bail out now. Otherwise pathological filters can spend time exponential
// in the number of primitives, e.g. if each primitive takes the
// previous primitive as its two inputs.
return;
}
mRequestedRect = mRequestedRect.Union(aRect);
RequestFromInputsForRect(aRect);
}
IntRect FilterNodeSoftware::MapInputRectToSource(uint32_t aInputEnumIndex,
const IntRect& aRect,
const IntRect& aMax,
FilterNode* aSourceNode) {
int32_t inputIndex = InputIndex(aInputEnumIndex);
if (inputIndex < 0) {
gfxDevCrash(LogReason::FilterInputError)
<< "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs();
return aMax;
}
if ((uint32_t)inputIndex < NumberOfSetInputs()) {
RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex];
// If we have any input filters call into them to do the mapping,
// otherwise we can assume an input surface will be used
// and just return aRect.
if (filter) {
return filter->MapRectToSource(aRect, aMax, aSourceNode);
}
}
// We have an input surface instead of a filter
// so check if we're the target node.
if (this == aSourceNode) {
return aRect;
}
return IntRect();
}
void FilterNodeSoftware::RequestInputRect(uint32_t aInputEnumIndex,
const IntRect& aRect) {
if (aRect.Overflows()) {
return;
}
int32_t inputIndex = InputIndex(aInputEnumIndex);
if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) {
gfxDevCrash(LogReason::FilterInputError)
<< "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs();
return;
}
if (mInputSurfaces[inputIndex]) {
return;
}
RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex];
MOZ_ASSERT(filter, "missing input");
filter->RequestRect(filter->GetOutputRectInRect(aRect));
}
SurfaceFormat FilterNodeSoftware::DesiredFormat(SurfaceFormat aCurrentFormat,
FormatHint aFormatHint) {
if (aCurrentFormat == SurfaceFormat::A8 && aFormatHint == CAN_HANDLE_A8) {
return SurfaceFormat::A8;
}
return SurfaceFormat::B8G8R8A8;
}
already_AddRefed<DataSourceSurface>
FilterNodeSoftware::GetInputDataSourceSurface(
uint32_t aInputEnumIndex, const IntRect& aRect, FormatHint aFormatHint,
ConvolveMatrixEdgeMode aEdgeMode,
const IntRect* aTransparencyPaddedSourceRect) {
if (aRect.Overflows()) {
return nullptr;
}
#ifdef DEBUG_DUMP_SURFACES
printf(
"<section><h1>GetInputDataSourceSurface with aRect: %d, %d, %d, "
"%d</h1>\n",
aRect.x, aRect.y, aRect.Width(), aRect.Height());
#endif
int32_t inputIndex = InputIndex(aInputEnumIndex);
if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) {
gfxDevCrash(LogReason::FilterInputData)
<< "Invalid data " << inputIndex << " vs. " << NumberOfSetInputs();
return nullptr;
}
if (aRect.IsEmpty()) {
return nullptr;
}
RefPtr<SourceSurface> surface;
IntRect surfaceRect;
if (mInputSurfaces[inputIndex]) {
// Input from input surface
surface = mInputSurfaces[inputIndex];
#ifdef DEBUG_DUMP_SURFACES
printf("input from input surface:\n");
#endif
surfaceRect = surface->GetRect();
} else {
// Input from input filter
#ifdef DEBUG_DUMP_SURFACES
printf("getting input from input filter %s...\n",
mInputFilters[inputIndex]->GetName());
#endif
RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex];
MOZ_ASSERT(filter, "missing input");
IntRect inputFilterOutput = filter->GetOutputRectInRect(aRect);
if (!inputFilterOutput.IsEmpty()) {
surface = filter->GetOutput(inputFilterOutput);
}
#ifdef DEBUG_DUMP_SURFACES
printf("input from input filter %s:\n",
mInputFilters[inputIndex]->GetName());
#endif
surfaceRect = inputFilterOutput;
MOZ_ASSERT(!surface || surfaceRect.Size() == surface->GetSize());
}
if (surface && surface->GetFormat() == SurfaceFormat::UNKNOWN) {
#ifdef DEBUG_DUMP_SURFACES
printf("wrong input format</section>\n\n");
#endif
return nullptr;
}
if (!surfaceRect.IsEmpty() && !surface) {
#ifdef DEBUG_DUMP_SURFACES
printf(" -- no input --</section>\n\n");
#endif
return nullptr;
}
if (aTransparencyPaddedSourceRect &&
!aTransparencyPaddedSourceRect->IsEmpty()) {
IntRect srcRect = aTransparencyPaddedSourceRect->Intersect(aRect);
surface =
GetDataSurfaceInRect(surface, surfaceRect, srcRect, EDGE_MODE_NONE);
if (surface) {
surfaceRect = srcRect;
} else {
// Padding the surface with transparency failed, probably due to size
// restrictions. Since |surface| is now null, set the surfaceRect to
// empty so that we're consistent.
surfaceRect.SetEmpty();
}
}
RefPtr<DataSourceSurface> result =
GetDataSurfaceInRect(surface, surfaceRect, aRect, aEdgeMode);
if (result) {
// TODO: This isn't safe since we don't have a guarantee
// that future Maps will have the same stride
DataSourceSurface::MappedSurface map;
if (result->Map(DataSourceSurface::READ, &map)) {
// Unmap immediately since CloneAligned hasn't been updated
// to use the Map API yet. We can still read the stride/data
// values as long as we don't try to dereference them.
result->Unmap();
if (map.mStride != GetAlignedStride<16>(map.mStride, 1) ||
reinterpret_cast<uintptr_t>(map.mData) % 16 != 0) {
// Align unaligned surface.
result = CloneAligned(result);
}
} else {
result = nullptr;
}
}
if (!result) {
#ifdef DEBUG_DUMP_SURFACES
printf(" -- no input --</section>\n\n");
#endif
return nullptr;
}
SurfaceFormat currentFormat = result->GetFormat();
if (DesiredFormat(currentFormat, aFormatHint) == SurfaceFormat::B8G8R8A8 &&
currentFormat != SurfaceFormat::B8G8R8A8) {
result = FilterProcessing::ConvertToB8G8R8A8(result);
}
#ifdef DEBUG_DUMP_SURFACES
printf("<img src='");
gfxUtils::DumpAsDataURL(result);
printf("'></section>");
#endif
MOZ_ASSERT(!result || result->GetSize() == aRect.Size(),
"wrong surface size");
return result.forget();
}
IntRect FilterNodeSoftware::GetInputRectInRect(uint32_t aInputEnumIndex,
const IntRect& aInRect) {
if (aInRect.Overflows()) {
return IntRect();
}
int32_t inputIndex = InputIndex(aInputEnumIndex);
if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) {
gfxDevCrash(LogReason::FilterInputRect)
<< "Invalid rect " << inputIndex << " vs. " << NumberOfSetInputs();
return IntRect();
}
if (mInputSurfaces[inputIndex]) {
return aInRect.Intersect(mInputSurfaces[inputIndex]->GetRect());
}
RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex];
MOZ_ASSERT(filter, "missing input");
return filter->GetOutputRectInRect(aInRect);
}
size_t FilterNodeSoftware::NumberOfSetInputs() {
return std::max(mInputSurfaces.size(), mInputFilters.size());
}
void FilterNodeSoftware::AddInvalidationListener(
FilterInvalidationListener* aListener) {
MOZ_ASSERT(aListener, "null listener");
mInvalidationListeners.push_back(aListener);
}
void FilterNodeSoftware::RemoveInvalidationListener(
FilterInvalidationListener* aListener) {
MOZ_ASSERT(aListener, "null listener");
std::vector<FilterInvalidationListener*>::iterator it = std::find(
mInvalidationListeners.begin(), mInvalidationListeners.end(), aListener);
mInvalidationListeners.erase(it);
}
void FilterNodeSoftware::FilterInvalidated(FilterNodeSoftware* aFilter) {
Invalidate();
}
void FilterNodeSoftware::Invalidate() {
mCachedOutput = nullptr;
mCachedRect = IntRect();
for (std::vector<FilterInvalidationListener*>::iterator it =
mInvalidationListeners.begin();
it != mInvalidationListeners.end(); it++) {
(*it)->FilterInvalidated(this);
}
}
FilterNodeSoftware::FilterNodeSoftware() {}
FilterNodeSoftware::~FilterNodeSoftware() {
MOZ_ASSERT(
mInvalidationListeners.empty(),
"All invalidation listeners should have unsubscribed themselves by now!");
for (std::vector<RefPtr<FilterNodeSoftware> >::iterator it =
mInputFilters.begin();
it != mInputFilters.end(); it++) {
if (*it) {
(*it)->RemoveInvalidationListener(this);
}
}
}
void FilterNodeSoftware::SetInput(uint32_t aIndex, FilterNode* aFilter) {
if (aFilter && aFilter->GetBackendType() != FILTER_BACKEND_SOFTWARE) {
MOZ_ASSERT(false, "can only take software filters as inputs");
return;
}
SetInput(aIndex, nullptr, static_cast<FilterNodeSoftware*>(aFilter));
}
void FilterNodeSoftware::SetInput(uint32_t aIndex, SourceSurface* aSurface) {
SetInput(aIndex, aSurface, nullptr);
}
void FilterNodeSoftware::SetInput(uint32_t aInputEnumIndex,
SourceSurface* aSurface,
FilterNodeSoftware* aFilter) {
int32_t inputIndex = InputIndex(aInputEnumIndex);
if (inputIndex < 0) {
gfxDevCrash(LogReason::FilterInputSet) << "Invalid set " << inputIndex;
return;
}
if ((uint32_t)inputIndex >= NumberOfSetInputs()) {
mInputSurfaces.resize(inputIndex + 1);
mInputFilters.resize(inputIndex + 1);
}
mInputSurfaces[inputIndex] = aSurface;
if (mInputFilters[inputIndex]) {
mInputFilters[inputIndex]->RemoveInvalidationListener(this);
}
if (aFilter) {
aFilter->AddInvalidationListener(this);
}
mInputFilters[inputIndex] = aFilter;
if (!aSurface && !aFilter && (size_t)inputIndex == NumberOfSetInputs()) {
mInputSurfaces.resize(inputIndex);
mInputFilters.resize(inputIndex);
}
Invalidate();
}
FilterNodeBlendSoftware::FilterNodeBlendSoftware()
: mBlendMode(BLEND_MODE_MULTIPLY) {}
int32_t FilterNodeBlendSoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_BLEND_IN:
return 0;
case IN_BLEND_IN2:
return 1;
default:
return -1;
}
}
void FilterNodeBlendSoftware::SetAttribute(uint32_t aIndex,
uint32_t aBlendMode) {
MOZ_ASSERT(aIndex == ATT_BLEND_BLENDMODE);
mBlendMode = static_cast<BlendMode>(aBlendMode);
Invalidate();
}
static CompositionOp ToBlendOp(BlendMode aOp) {
switch (aOp) {
case BLEND_MODE_MULTIPLY:
return CompositionOp::OP_MULTIPLY;
case BLEND_MODE_SCREEN:
return CompositionOp::OP_SCREEN;
case BLEND_MODE_OVERLAY:
return CompositionOp::OP_OVERLAY;
case BLEND_MODE_DARKEN:
return CompositionOp::OP_DARKEN;
case BLEND_MODE_LIGHTEN:
return CompositionOp::OP_LIGHTEN;
case BLEND_MODE_COLOR_DODGE:
return CompositionOp::OP_COLOR_DODGE;
case BLEND_MODE_COLOR_BURN:
return CompositionOp::OP_COLOR_BURN;
case BLEND_MODE_HARD_LIGHT:
return CompositionOp::OP_HARD_LIGHT;
case BLEND_MODE_SOFT_LIGHT:
return CompositionOp::OP_SOFT_LIGHT;
case BLEND_MODE_DIFFERENCE:
return CompositionOp::OP_DIFFERENCE;
case BLEND_MODE_EXCLUSION:
return CompositionOp::OP_EXCLUSION;
case BLEND_MODE_HUE:
return CompositionOp::OP_HUE;
case BLEND_MODE_SATURATION:
return CompositionOp::OP_SATURATION;
case BLEND_MODE_COLOR:
return CompositionOp::OP_COLOR;
case BLEND_MODE_LUMINOSITY:
return CompositionOp::OP_LUMINOSITY;
}
MOZ_ASSERT_UNREACHABLE("Unexpected BlendMode");
return CompositionOp::OP_OVER;
}
already_AddRefed<DataSourceSurface> FilterNodeBlendSoftware::Render(
const IntRect& aRect) {
RefPtr<DataSourceSurface> input1 =
GetInputDataSourceSurface(IN_BLEND_IN, aRect, NEED_COLOR_CHANNELS);
RefPtr<DataSourceSurface> input2 =
GetInputDataSourceSurface(IN_BLEND_IN2, aRect, NEED_COLOR_CHANNELS);
// Null inputs need to be treated as transparent.
// First case: both are transparent.
if (!input1 && !input2) {
// Then the result is transparent, too.
return nullptr;
}
// Second case: one of them is transparent. Return the non-transparent one.
if (!input1 || !input2) {
return input1 ? input1.forget() : input2.forget();
}
// Third case: both are non-transparent.
// Apply normal filtering.
RefPtr<DataSourceSurface> target =
FilterProcessing::ApplyBlending(input1, input2, mBlendMode);
if (target != nullptr) {
return target.forget();
}
IntSize size = input1->GetSize();
target = Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
CopyRect(input1, target, IntRect(IntPoint(), size), IntPoint());
// This needs to stay in scope until the draw target has been flushed.
DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::READ_WRITE);
if (MOZ2D_WARN_IF(!targetMap.IsMapped())) {
return nullptr;
}
RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
BackendType::SKIA, targetMap.GetData(), target->GetSize(),
targetMap.GetStride(), target->GetFormat());
if (!dt) {
gfxWarning()
<< "FilterNodeBlendSoftware::Render failed in CreateDrawTargetForData";
return nullptr;
}
Rect r(0, 0, size.width, size.height);
dt->DrawSurface(input2, r, r, DrawSurfaceOptions(),
DrawOptions(1.0f, ToBlendOp(mBlendMode)));
dt->Flush();
return target.forget();
}
void FilterNodeBlendSoftware::RequestFromInputsForRect(const IntRect& aRect) {
RequestInputRect(IN_BLEND_IN, aRect);
RequestInputRect(IN_BLEND_IN2, aRect);
}
IntRect FilterNodeBlendSoftware::MapRectToSource(const IntRect& aRect,
const IntRect& aMax,
FilterNode* aSourceNode) {
IntRect result = MapInputRectToSource(IN_BLEND_IN, aRect, aMax, aSourceNode);
result.OrWith(MapInputRectToSource(IN_BLEND_IN2, aRect, aMax, aSourceNode));
return result;
}
IntRect FilterNodeBlendSoftware::GetOutputRectInRect(const IntRect& aRect) {
return GetInputRectInRect(IN_BLEND_IN, aRect)
.Union(GetInputRectInRect(IN_BLEND_IN2, aRect))
.Intersect(aRect);
}
FilterNodeTransformSoftware::FilterNodeTransformSoftware()
: mSamplingFilter(SamplingFilter::GOOD) {}
int32_t FilterNodeTransformSoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_TRANSFORM_IN:
return 0;
default:
return -1;
}
}
void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex,
uint32_t aFilter) {
MOZ_ASSERT(aIndex == ATT_TRANSFORM_FILTER);
mSamplingFilter = static_cast<SamplingFilter>(aFilter);
Invalidate();
}
void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex,
const Matrix& aMatrix) {
MOZ_ASSERT(aIndex == ATT_TRANSFORM_MATRIX);
mMatrix = aMatrix;
Invalidate();
}
IntRect FilterNodeTransformSoftware::SourceRectForOutputRect(
const IntRect& aRect) {
if (aRect.IsEmpty()) {
return IntRect();
}
Matrix inverted(mMatrix);
if (!inverted.Invert()) {
return IntRect();
}
Rect neededRect = inverted.TransformBounds(Rect(aRect));
neededRect.RoundOut();
IntRect neededIntRect;
if (!neededRect.ToIntRect(&neededIntRect)) {
return IntRect();
}
return GetInputRectInRect(IN_TRANSFORM_IN, neededIntRect);
}
IntRect FilterNodeTransformSoftware::MapRectToSource(const IntRect& aRect,
const IntRect& aMax,
FilterNode* aSourceNode) {
if (aRect.IsEmpty()) {
return IntRect();
}
Matrix inverted(mMatrix);
if (!inverted.Invert()) {
return aMax;
}
Rect neededRect = inverted.TransformBounds(Rect(aRect));
neededRect.RoundOut();
IntRect neededIntRect;
if (!neededRect.ToIntRect(&neededIntRect)) {
return aMax;
}
return MapInputRectToSource(IN_TRANSFORM_IN, neededIntRect, aMax,
aSourceNode);
}
already_AddRefed<DataSourceSurface> FilterNodeTransformSoftware::Render(
const IntRect& aRect) {
IntRect srcRect = SourceRectForOutputRect(aRect);
RefPtr<DataSourceSurface> input =
GetInputDataSourceSurface(IN_TRANSFORM_IN, srcRect);
if (!input) {
return nullptr;
}
Matrix transform = Matrix::Translation(srcRect.X(), srcRect.Y()) * mMatrix *
Matrix::Translation(-aRect.X(), -aRect.Y());
if (transform.IsIdentity() && srcRect.Size() == aRect.Size()) {
return input.forget();
}
RefPtr<DataSourceSurface> surf =
Factory::CreateDataSourceSurface(aRect.Size(), input->GetFormat(), true);
if (!surf) {
return nullptr;
}
DataSourceSurface::MappedSurface mapping;
if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) {
gfxCriticalError()
<< "FilterNodeTransformSoftware::Render failed to map surface";
return nullptr;
}
RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
BackendType::SKIA, mapping.mData, surf->GetSize(), mapping.mStride,
surf->GetFormat());
if (!dt) {
gfxWarning() << "FilterNodeTransformSoftware::Render failed in "
"CreateDrawTargetForData";
return nullptr;
}
Rect r(0, 0, srcRect.Width(), srcRect.Height());
dt->SetTransform(transform);
dt->DrawSurface(input, r, r, DrawSurfaceOptions(mSamplingFilter));
dt->Flush();
surf->Unmap();
return surf.forget();
}
void FilterNodeTransformSoftware::RequestFromInputsForRect(
const IntRect& aRect) {
RequestInputRect(IN_TRANSFORM_IN, SourceRectForOutputRect(aRect));
}
IntRect FilterNodeTransformSoftware::GetOutputRectInRect(const IntRect& aRect) {
IntRect srcRect = SourceRectForOutputRect(aRect);
if (srcRect.IsEmpty()) {
return IntRect();
}
Rect outRect = mMatrix.TransformBounds(Rect(srcRect));
outRect.RoundOut();
IntRect outIntRect;
if (!outRect.ToIntRect(&outIntRect)) {
return IntRect();
}
return outIntRect.Intersect(aRect);
}
FilterNodeMorphologySoftware::FilterNodeMorphologySoftware()
: mOperator(MORPHOLOGY_OPERATOR_ERODE) {}
int32_t FilterNodeMorphologySoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_MORPHOLOGY_IN:
return 0;
default:
return -1;
}
}
void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex,
const IntSize& aRadii) {
MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_RADII);
mRadii.width = std::min(std::max(aRadii.width, 0), 100000);
mRadii.height = std::min(std::max(aRadii.height, 0), 100000);
Invalidate();
}
void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex,
uint32_t aOperator) {
MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_OPERATOR);
mOperator = static_cast<MorphologyOperator>(aOperator);
Invalidate();
}
static already_AddRefed<DataSourceSurface> ApplyMorphology(
const IntRect& aSourceRect, DataSourceSurface* aInput,
const IntRect& aDestRect, int32_t rx, int32_t ry,
MorphologyOperator aOperator) {
IntRect srcRect = aSourceRect - aDestRect.TopLeft();
IntRect destRect = aDestRect - aDestRect.TopLeft();
IntRect tmpRect(destRect.X(), srcRect.Y(), destRect.Width(),
srcRect.Height());
#ifdef DEBUG
IntMargin margin = srcRect - destRect;
MOZ_ASSERT(margin.top >= ry && margin.right >= rx && margin.bottom >= ry &&
margin.left >= rx,
"insufficient margin");
#endif
RefPtr<DataSourceSurface> tmp;
if (rx == 0) {
tmp = aInput;
} else {
tmp = Factory::CreateDataSourceSurface(tmpRect.Size(),
SurfaceFormat::B8G8R8A8);
if (MOZ2D_WARN_IF(!tmp)) {
return nullptr;
}
DataSourceSurface::ScopedMap sourceMap(aInput, DataSourceSurface::READ);
DataSourceSurface::ScopedMap tmpMap(tmp, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !tmpMap.IsMapped())) {
return nullptr;
}
uint8_t* sourceData = DataAtOffset(aInput, sourceMap.GetMappedSurface(),
destRect.TopLeft() - srcRect.TopLeft());
uint8_t* tmpData = DataAtOffset(tmp, tmpMap.GetMappedSurface(),
destRect.TopLeft() - tmpRect.TopLeft());
FilterProcessing::ApplyMorphologyHorizontal(
sourceData, sourceMap.GetStride(), tmpData, tmpMap.GetStride(), tmpRect,
rx, aOperator);
}
RefPtr<DataSourceSurface> dest;
if (ry == 0) {
dest = tmp;
} else {
dest = Factory::CreateDataSourceSurface(destRect.Size(),
SurfaceFormat::B8G8R8A8);
if (MOZ2D_WARN_IF(!dest)) {
return nullptr;
}
DataSourceSurface::ScopedMap tmpMap(tmp, DataSourceSurface::READ);
DataSourceSurface::ScopedMap destMap(dest, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!tmpMap.IsMapped() || !destMap.IsMapped())) {
return nullptr;
}
int32_t tmpStride = tmpMap.GetStride();
uint8_t* tmpData = DataAtOffset(tmp, tmpMap.GetMappedSurface(),
destRect.TopLeft() - tmpRect.TopLeft());
int32_t destStride = destMap.GetStride();
uint8_t* destData = destMap.GetData();
FilterProcessing::ApplyMorphologyVertical(
tmpData, tmpStride, destData, destStride, destRect, ry, aOperator);
}
return dest.forget();
}
already_AddRefed<DataSourceSurface> FilterNodeMorphologySoftware::Render(
const IntRect& aRect) {
IntRect srcRect = aRect;
srcRect.Inflate(mRadii);
RefPtr<DataSourceSurface> input =
GetInputDataSourceSurface(IN_MORPHOLOGY_IN, srcRect, NEED_COLOR_CHANNELS);
if (!input) {
return nullptr;
}
int32_t rx = mRadii.width;
int32_t ry = mRadii.height;
if (rx == 0 && ry == 0) {
return input.forget();
}
return ApplyMorphology(srcRect, input, aRect, rx, ry, mOperator);
}
void FilterNodeMorphologySoftware::RequestFromInputsForRect(
const IntRect& aRect) {
IntRect srcRect = aRect;
srcRect.Inflate(mRadii);
RequestInputRect(IN_MORPHOLOGY_IN, srcRect);
}
IntRect FilterNodeMorphologySoftware::GetOutputRectInRect(
const IntRect& aRect) {
IntRect inflatedSourceRect = aRect;
inflatedSourceRect.Inflate(mRadii);
IntRect inputRect = GetInputRectInRect(IN_MORPHOLOGY_IN, inflatedSourceRect);
if (mOperator == MORPHOLOGY_OPERATOR_ERODE) {
inputRect.Deflate(mRadii);
} else {
inputRect.Inflate(mRadii);
}
return inputRect.Intersect(aRect);
}
int32_t FilterNodeColorMatrixSoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_COLOR_MATRIX_IN:
return 0;
default:
return -1;
}
}
void FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex,
const Matrix5x4& aMatrix) {
MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_MATRIX);
mMatrix = aMatrix;
Invalidate();
}
void FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex,
uint32_t aAlphaMode) {
MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_ALPHA_MODE);
mAlphaMode = (AlphaMode)aAlphaMode;
Invalidate();
}
static already_AddRefed<DataSourceSurface> Premultiply(
DataSourceSurface* aSurface) {
if (aSurface->GetFormat() == SurfaceFormat::A8) {
RefPtr<DataSourceSurface> surface(aSurface);
return surface.forget();
}
IntSize size = aSurface->GetSize();
RefPtr<DataSourceSurface> target =
Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ);
DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) {
return nullptr;
}
uint8_t* inputData = inputMap.GetData();
int32_t inputStride = inputMap.GetStride();
uint8_t* targetData = targetMap.GetData();
int32_t targetStride = targetMap.GetStride();
FilterProcessing::DoPremultiplicationCalculation(
size, targetData, targetStride, inputData, inputStride);
return target.forget();
}
static already_AddRefed<DataSourceSurface> Unpremultiply(
DataSourceSurface* aSurface) {
if (aSurface->GetFormat() == SurfaceFormat::A8) {
RefPtr<DataSourceSurface> surface(aSurface);
return surface.forget();
}
IntSize size = aSurface->GetSize();
RefPtr<DataSourceSurface> target =
Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ);
DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) {
return nullptr;
}
uint8_t* inputData = inputMap.GetData();
int32_t inputStride = inputMap.GetStride();
uint8_t* targetData = targetMap.GetData();
int32_t targetStride = targetMap.GetStride();
FilterProcessing::DoUnpremultiplicationCalculation(
size, targetData, targetStride, inputData, inputStride);
return target.forget();
}
static already_AddRefed<DataSourceSurface> Opacity(DataSourceSurface* aSurface,
Float aValue) {
if (aValue == 1.0f) {
RefPtr<DataSourceSurface> surface(aSurface);
return surface.forget();
}
IntSize size = aSurface->GetSize();
RefPtr<DataSourceSurface> target =
Factory::CreateDataSourceSurface(size, aSurface->GetFormat());
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ);
DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) {
return nullptr;
}
uint8_t* inputData = inputMap.GetData();
int32_t inputStride = inputMap.GetStride();
uint8_t* targetData = targetMap.GetData();
int32_t targetStride = targetMap.GetStride();
if (aSurface->GetFormat() == SurfaceFormat::A8) {
FilterProcessing::DoOpacityCalculationA8(size, targetData, targetStride,
inputData, inputStride, aValue);
} else {
MOZ_ASSERT(aSurface->GetFormat() == SurfaceFormat::B8G8R8A8);
FilterProcessing::DoOpacityCalculation(size, targetData, targetStride,
inputData, inputStride, aValue);
}
return target.forget();
}
already_AddRefed<DataSourceSurface> FilterNodeColorMatrixSoftware::Render(
const IntRect& aRect) {
RefPtr<DataSourceSurface> input =
GetInputDataSourceSurface(IN_COLOR_MATRIX_IN, aRect, NEED_COLOR_CHANNELS);
if (!input) {
return nullptr;
}
if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) {
input = Unpremultiply(input);
}
RefPtr<DataSourceSurface> result =
FilterProcessing::ApplyColorMatrix(input, mMatrix);
if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) {
result = Premultiply(result);
}
return result.forget();
}
void FilterNodeColorMatrixSoftware::