gecko-dev/gfx/2d/FilterNodeSoftware.cpp

3757 строки
122 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#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;
// Lock the cache and retrieve a cached surface if we have one and it can
// satisfy this request, or else request a rect we will compute and cache
{
MutexAutoLock lock(mCacheMutex);
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
MutexAutoLock lock(mCacheMutex);
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() {
MutexAutoLock lock(mCacheMutex);
mCachedOutput = nullptr;
mCachedRect = IntRect();
for (std::vector<FilterInvalidationListener*>::iterator it =
mInvalidationListeners.begin();
it != mInvalidationListeners.end(); it++) {
(*it)->FilterInvalidated(this);
}
}
FilterNodeSoftware::FilterNodeSoftware()
: mCacheMutex("FilterNodeSoftware::mCacheMutex") {}
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;
default:
return CompositionOp::OP_OVER;
}
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::RequestFromInputsForRect(
const IntRect& aRect) {
RequestInputRect(IN_COLOR_MATRIX_IN, aRect);
}
IntRect FilterNodeColorMatrixSoftware::MapRectToSource(
const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
return MapInputRectToSource(IN_COLOR_MATRIX_IN, aRect, aMax, aSourceNode);
}
IntRect FilterNodeColorMatrixSoftware::GetOutputRectInRect(
const IntRect& aRect) {
if (mMatrix._54 > 0.0f) {
return aRect;
}
return GetInputRectInRect(IN_COLOR_MATRIX_IN, aRect);
}
void FilterNodeFloodSoftware::SetAttribute(uint32_t aIndex,
const DeviceColor& aColor) {
MOZ_ASSERT(aIndex == ATT_FLOOD_COLOR);
mColor = aColor;
Invalidate();
}
static uint32_t ColorToBGRA(const DeviceColor& aColor) {
union {
uint32_t color;
uint8_t components[4];
};
components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] =
NS_lround(aColor.r * aColor.a * 255.0f);
components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] =
NS_lround(aColor.g * aColor.a * 255.0f);
components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] =
NS_lround(aColor.b * aColor.a * 255.0f);
components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = NS_lround(aColor.a * 255.0f);
return color;
}
static SurfaceFormat FormatForColor(DeviceColor aColor) {
if (aColor.r == 0 && aColor.g == 0 && aColor.b == 0) {
return SurfaceFormat::A8;
}
return SurfaceFormat::B8G8R8A8;
}
already_AddRefed<DataSourceSurface> FilterNodeFloodSoftware::Render(
const IntRect& aRect) {
SurfaceFormat format = FormatForColor(mColor);
RefPtr<DataSourceSurface> target =
Factory::CreateDataSourceSurface(aRect.Size(), format);
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!targetMap.IsMapped())) {
return nullptr;
}
uint8_t* targetData = targetMap.GetData();
int32_t stride = targetMap.GetStride();
if (format == SurfaceFormat::B8G8R8A8) {
uint32_t color = ColorToBGRA(mColor);
for (int32_t y = 0; y < aRect.Height(); y++) {
for (int32_t x = 0; x < aRect.Width(); x++) {
*((uint32_t*)targetData + x) = color;
}
PodZero(&targetData[aRect.Width() * 4], stride - aRect.Width() * 4);
targetData += stride;
}
} else if (format == SurfaceFormat::A8) {
uint8_t alpha = NS_lround(mColor.a * 255.0f);
for (int32_t y = 0; y < aRect.Height(); y++) {
for (int32_t x = 0; x < aRect.Width(); x++) {
targetData[x] = alpha;
}
PodZero(&targetData[aRect.Width()], stride - aRect.Width());
targetData += stride;
}
} else {
gfxDevCrash(LogReason::FilterInputFormat)
<< "Bad format in flood render " << (int)format;
return nullptr;
}
return target.forget();
}
// Override GetOutput to get around caching. Rendering simple floods is
// comparatively fast.
already_AddRefed<DataSourceSurface> FilterNodeFloodSoftware::GetOutput(
const IntRect& aRect) {
return Render(aRect);
}
IntRect FilterNodeFloodSoftware::MapRectToSource(const IntRect& aRect,
const IntRect& aMax,
FilterNode* aSourceNode) {
return IntRect();
}
IntRect FilterNodeFloodSoftware::GetOutputRectInRect(const IntRect& aRect) {
if (mColor.a == 0.0f) {
return IntRect();
}
return aRect;
}
int32_t FilterNodeTileSoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_TILE_IN:
return 0;
default:
return -1;
}
}
void FilterNodeTileSoftware::SetAttribute(uint32_t aIndex,
const IntRect& aSourceRect) {
MOZ_ASSERT(aIndex == ATT_TILE_SOURCE_RECT);
mSourceRect.SetRect(int32_t(aSourceRect.X()), int32_t(aSourceRect.Y()),
int32_t(aSourceRect.Width()),
int32_t(aSourceRect.Height()));
Invalidate();
}
namespace {
struct CompareIntRects {
bool operator()(const IntRect& a, const IntRect& b) const {
if (a.X() != b.X()) {
return a.X() < b.X();
}
if (a.Y() != b.Y()) {
return a.Y() < b.Y();
}
if (a.Width() != b.Width()) {
return a.Width() < b.Width();
}
return a.Height() < b.Height();
}
};
} // namespace
already_AddRefed<DataSourceSurface> FilterNodeTileSoftware::Render(
const IntRect& aRect) {
if (mSourceRect.IsEmpty()) {
return nullptr;
}
if (mSourceRect.Contains(aRect)) {
return GetInputDataSourceSurface(IN_TILE_IN, aRect);
}
RefPtr<DataSourceSurface> target;
typedef std::map<IntRect, RefPtr<DataSourceSurface>, CompareIntRects>
InputMap;
InputMap inputs;
IntPoint startIndex = TileIndex(mSourceRect, aRect.TopLeft());
IntPoint endIndex = TileIndex(mSourceRect, aRect.BottomRight());
for (int32_t ix = startIndex.x; ix <= endIndex.x; ix++) {
for (int32_t iy = startIndex.y; iy <= endIndex.y; iy++) {
IntPoint sourceToDestOffset(ix * mSourceRect.Width(),
iy * mSourceRect.Height());
IntRect destRect = aRect.Intersect(mSourceRect + sourceToDestOffset);
IntRect srcRect = destRect - sourceToDestOffset;
if (srcRect.IsEmpty()) {
continue;
}
RefPtr<DataSourceSurface> input;
InputMap::iterator it = inputs.find(srcRect);
if (it == inputs.end()) {
input = GetInputDataSourceSurface(IN_TILE_IN, srcRect);
inputs[srcRect] = input;
} else {
input = it->second;
}
if (!input) {
return nullptr;
}
if (!target) {
// We delay creating the target until now because we want to use the
// same format as our input filter, and we do not actually know the
// input format before we call GetInputDataSourceSurface.
target =
Factory::CreateDataSourceSurface(aRect.Size(), input->GetFormat());
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
}
if (input->GetFormat() != target->GetFormat()) {
// Different rectangles of the input can have different formats. If
// that happens, just convert everything to B8G8R8A8.
target = FilterProcessing::ConvertToB8G8R8A8(target);
input = FilterProcessing::ConvertToB8G8R8A8(input);
if (MOZ2D_WARN_IF(!target) || MOZ2D_WARN_IF(!input)) {
return nullptr;
}
}
CopyRect(input, target, srcRect - srcRect.TopLeft(),
destRect.TopLeft() - aRect.TopLeft());
}
}
return target.forget();
}
void FilterNodeTileSoftware::RequestFromInputsForRect(const IntRect& aRect) {
// Do not request anything.
// Source rects for the tile filter can be discontinuous with large gaps
// between them. Requesting those from our input filter might cause it to
// render the whole bounding box of all of them, which would be wasteful.
}
IntRect FilterNodeTileSoftware::GetOutputRectInRect(const IntRect& aRect) {
return aRect;
}
FilterNodeComponentTransferSoftware::FilterNodeComponentTransferSoftware()
: mDisableR(true), mDisableG(true), mDisableB(true), mDisableA(true) {}
void FilterNodeComponentTransferSoftware::SetAttribute(uint32_t aIndex,
bool aDisable) {
switch (aIndex) {
case ATT_TRANSFER_DISABLE_R:
mDisableR = aDisable;
break;
case ATT_TRANSFER_DISABLE_G:
mDisableG = aDisable;
break;
case ATT_TRANSFER_DISABLE_B:
mDisableB = aDisable;
break;
case ATT_TRANSFER_DISABLE_A:
mDisableA = aDisable;
break;
default:
MOZ_CRASH("GFX: FilterNodeComponentTransferSoftware::SetAttribute");
}
Invalidate();
}
void FilterNodeComponentTransferSoftware::GenerateLookupTable(
ptrdiff_t aComponent, uint8_t aTables[4][256], bool aDisabled) {
if (aDisabled) {
for (int32_t i = 0; i < 256; ++i) {
aTables[aComponent][i] = i;
}
} else {
FillLookupTable(aComponent, aTables[aComponent]);
}
}
template <uint32_t BytesPerPixel>
static void TransferComponents(
DataSourceSurface* aInput, DataSourceSurface* aTarget,
const uint8_t aLookupTables[BytesPerPixel][256]) {
MOZ_ASSERT(aInput->GetFormat() == aTarget->GetFormat(), "different formats");
IntSize size = aInput->GetSize();
DataSourceSurface::ScopedMap sourceMap(aInput, DataSourceSurface::READ);
DataSourceSurface::ScopedMap targetMap(aTarget, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !targetMap.IsMapped())) {
return;
}
uint8_t* sourceData = sourceMap.GetData();
int32_t sourceStride = sourceMap.GetStride();
uint8_t* targetData = targetMap.GetData();
int32_t targetStride = targetMap.GetStride();
MOZ_ASSERT(sourceStride <= targetStride, "target smaller than source");
for (int32_t y = 0; y < size.height; y++) {
for (int32_t x = 0; x < size.width; x++) {
uint32_t sourceIndex = y * sourceStride + x * BytesPerPixel;
uint32_t targetIndex = y * targetStride + x * BytesPerPixel;
for (uint32_t i = 0; i < BytesPerPixel; i++) {
targetData[targetIndex + i] =
aLookupTables[i][sourceData[sourceIndex + i]];
}
}
// Zero padding to keep valgrind happy.
PodZero(&targetData[y * targetStride + size.width * BytesPerPixel],
targetStride - size.width * BytesPerPixel);
}
}
static bool IsAllZero(const uint8_t aLookupTable[256]) {
for (int32_t i = 0; i < 256; i++) {
if (aLookupTable[i] != 0) {
return false;
}
}
return true;
}
already_AddRefed<DataSourceSurface> FilterNodeComponentTransferSoftware::Render(
const IntRect& aRect) {
if (mDisableR && mDisableG && mDisableB && mDisableA) {
return GetInputDataSourceSurface(IN_TRANSFER_IN, aRect);
}
uint8_t lookupTables[4][256];
GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_R, lookupTables, mDisableR);
GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_G, lookupTables, mDisableG);
GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_B, lookupTables, mDisableB);
GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_A, lookupTables, mDisableA);
bool needColorChannels =
lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_R][0] != 0 ||
lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_G][0] != 0 ||
lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_B][0] != 0;
FormatHint pref = needColorChannels ? NEED_COLOR_CHANNELS : CAN_HANDLE_A8;
RefPtr<DataSourceSurface> input =
GetInputDataSourceSurface(IN_TRANSFER_IN, aRect, pref);
if (!input) {
return nullptr;
}
if (input->GetFormat() == SurfaceFormat::B8G8R8A8 && !needColorChannels) {
bool colorChannelsBecomeBlack =
IsAllZero(lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_R]) &&
IsAllZero(lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_G]) &&
IsAllZero(lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_B]);
if (colorChannelsBecomeBlack) {
input = FilterProcessing::ExtractAlpha(input);
}
}
SurfaceFormat format = input->GetFormat();
if (format == SurfaceFormat::A8 && mDisableA) {
return input.forget();
}
RefPtr<DataSourceSurface> target =
Factory::CreateDataSourceSurface(aRect.Size(), format);
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
if (format == SurfaceFormat::A8) {
TransferComponents<1>(input, target,
&lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_A]);
} else {
TransferComponents<4>(input, target, lookupTables);
}
return target.forget();
}
void FilterNodeComponentTransferSoftware::RequestFromInputsForRect(
const IntRect& aRect) {
RequestInputRect(IN_TRANSFER_IN, aRect);
}
IntRect FilterNodeComponentTransferSoftware::MapRectToSource(
const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
return MapInputRectToSource(IN_TRANSFER_IN, aRect, aMax, aSourceNode);
}
IntRect FilterNodeComponentTransferSoftware::GetOutputRectInRect(
const IntRect& aRect) {
if (mDisableA) {
return GetInputRectInRect(IN_TRANSFER_IN, aRect);
}
return aRect;
}
int32_t FilterNodeComponentTransferSoftware::InputIndex(
uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_TRANSFER_IN:
return 0;
default:
return -1;
}
}
void FilterNodeTableTransferSoftware::SetAttribute(uint32_t aIndex,
const Float* aFloat,
uint32_t aSize) {
std::vector<Float> table(aFloat, aFloat + aSize);
switch (aIndex) {
case ATT_TABLE_TRANSFER_TABLE_R:
mTableR = table;
break;
case ATT_TABLE_TRANSFER_TABLE_G:
mTableG = table;
break;
case ATT_TABLE_TRANSFER_TABLE_B:
mTableB = table;
break;
case ATT_TABLE_TRANSFER_TABLE_A:
mTableA = table;
break;
default:
MOZ_CRASH("GFX: FilterNodeTableTransferSoftware::SetAttribute");
}
Invalidate();
}
void FilterNodeTableTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
uint8_t aTable[256]) {
switch (aComponent) {
case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
FillLookupTableImpl(mTableR, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
FillLookupTableImpl(mTableG, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
FillLookupTableImpl(mTableB, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
FillLookupTableImpl(mTableA, aTable);
break;
default:
MOZ_ASSERT(false, "unknown component");
break;
}
}
void FilterNodeTableTransferSoftware::FillLookupTableImpl(
std::vector<Float>& aTableValues, uint8_t aTable[256]) {
uint32_t tvLength = aTableValues.size();
if (tvLength < 2) {
return;
}
for (size_t i = 0; i < 256; i++) {
uint32_t k = (i * (tvLength - 1)) / 255;
Float v1 = aTableValues[k];
Float v2 = aTableValues[std::min(k + 1, tvLength - 1)];
int32_t val = int32_t(255 * (v1 + (i / 255.0f - k / float(tvLength - 1)) *
(tvLength - 1) * (v2 - v1)));
val = std::min(255, val);
val = std::max(0, val);
aTable[i] = val;
}
}
void FilterNodeDiscreteTransferSoftware::SetAttribute(uint32_t aIndex,
const Float* aFloat,
uint32_t aSize) {
std::vector<Float> discrete(aFloat, aFloat + aSize);
switch (aIndex) {
case ATT_DISCRETE_TRANSFER_TABLE_R:
mTableR = discrete;
break;
case ATT_DISCRETE_TRANSFER_TABLE_G:
mTableG = discrete;
break;
case ATT_DISCRETE_TRANSFER_TABLE_B:
mTableB = discrete;
break;
case ATT_DISCRETE_TRANSFER_TABLE_A:
mTableA = discrete;
break;
default:
MOZ_CRASH("GFX: FilterNodeDiscreteTransferSoftware::SetAttribute");
}
Invalidate();
}
void FilterNodeDiscreteTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
uint8_t aTable[256]) {
switch (aComponent) {
case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
FillLookupTableImpl(mTableR, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
FillLookupTableImpl(mTableG, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
FillLookupTableImpl(mTableB, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
FillLookupTableImpl(mTableA, aTable);
break;
default:
MOZ_ASSERT(false, "unknown component");
break;
}
}
void FilterNodeDiscreteTransferSoftware::FillLookupTableImpl(
std::vector<Float>& aTableValues, uint8_t aTable[256]) {
uint32_t tvLength = aTableValues.size();
if (tvLength < 1) {
return;
}
for (size_t i = 0; i < 256; i++) {
uint32_t k = (i * tvLength) / 255;
k = std::min(k, tvLength - 1);
Float v = aTableValues[k];
int32_t val = NS_lround(255 * v);
val = std::min(255, val);
val = std::max(0, val);
aTable[i] = val;
}
}
FilterNodeLinearTransferSoftware::FilterNodeLinearTransferSoftware()
: mSlopeR(0),
mSlopeG(0),
mSlopeB(0),
mSlopeA(0),
mInterceptR(0),
mInterceptG(0),
mInterceptB(0),
mInterceptA(0) {}
void FilterNodeLinearTransferSoftware::SetAttribute(uint32_t aIndex,
Float aValue) {
switch (aIndex) {
case ATT_LINEAR_TRANSFER_SLOPE_R:
mSlopeR = aValue;
break;
case ATT_LINEAR_TRANSFER_INTERCEPT_R:
mInterceptR = aValue;
break;
case ATT_LINEAR_TRANSFER_SLOPE_G:
mSlopeG = aValue;
break;
case ATT_LINEAR_TRANSFER_INTERCEPT_G:
mInterceptG = aValue;
break;
case ATT_LINEAR_TRANSFER_SLOPE_B:
mSlopeB = aValue;
break;
case ATT_LINEAR_TRANSFER_INTERCEPT_B:
mInterceptB = aValue;
break;
case ATT_LINEAR_TRANSFER_SLOPE_A:
mSlopeA = aValue;
break;
case ATT_LINEAR_TRANSFER_INTERCEPT_A:
mInterceptA = aValue;
break;
default:
MOZ_CRASH("GFX: FilterNodeLinearTransferSoftware::SetAttribute");
}
Invalidate();
}
void FilterNodeLinearTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
uint8_t aTable[256]) {
switch (aComponent) {
case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
FillLookupTableImpl(mSlopeR, mInterceptR, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
FillLookupTableImpl(mSlopeG, mInterceptG, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
FillLookupTableImpl(mSlopeB, mInterceptB, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
FillLookupTableImpl(mSlopeA, mInterceptA, aTable);
break;
default:
MOZ_ASSERT(false, "unknown component");
break;
}
}
void FilterNodeLinearTransferSoftware::FillLookupTableImpl(
Float aSlope, Float aIntercept, uint8_t aTable[256]) {
for (size_t i = 0; i < 256; i++) {
int32_t val = NS_lround(aSlope * i + 255 * aIntercept);
val = std::min(255, val);
val = std::max(0, val);
aTable[i] = val;
}
}
FilterNodeGammaTransferSoftware::FilterNodeGammaTransferSoftware()
: mAmplitudeR(0),
mAmplitudeG(0),
mAmplitudeB(0),
mAmplitudeA(0),
mExponentR(0),
mExponentG(0),
mExponentB(0),
mExponentA(0),
mOffsetR(0.0),
mOffsetG(0.0),
mOffsetB(0.0),
mOffsetA(0.0) {}
void FilterNodeGammaTransferSoftware::SetAttribute(uint32_t aIndex,
Float aValue) {
switch (aIndex) {
case ATT_GAMMA_TRANSFER_AMPLITUDE_R:
mAmplitudeR = aValue;
break;
case ATT_GAMMA_TRANSFER_EXPONENT_R:
mExponentR = aValue;
break;
case ATT_GAMMA_TRANSFER_OFFSET_R:
mOffsetR = aValue;
break;
case ATT_GAMMA_TRANSFER_AMPLITUDE_G:
mAmplitudeG = aValue;
break;
case ATT_GAMMA_TRANSFER_EXPONENT_G:
mExponentG = aValue;
break;
case ATT_GAMMA_TRANSFER_OFFSET_G:
mOffsetG = aValue;
break;
case ATT_GAMMA_TRANSFER_AMPLITUDE_B:
mAmplitudeB = aValue;
break;
case ATT_GAMMA_TRANSFER_EXPONENT_B:
mExponentB = aValue;
break;
case ATT_GAMMA_TRANSFER_OFFSET_B:
mOffsetB = aValue;
break;
case ATT_GAMMA_TRANSFER_AMPLITUDE_A:
mAmplitudeA = aValue;
break;
case ATT_GAMMA_TRANSFER_EXPONENT_A:
mExponentA = aValue;
break;
case ATT_GAMMA_TRANSFER_OFFSET_A:
mOffsetA = aValue;
break;
default:
MOZ_CRASH("GFX: FilterNodeGammaTransferSoftware::SetAttribute");
}
Invalidate();
}
void FilterNodeGammaTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
uint8_t aTable[256]) {
switch (aComponent) {
case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
FillLookupTableImpl(mAmplitudeR, mExponentR, mOffsetR, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_G:
FillLookupTableImpl(mAmplitudeG, mExponentG, mOffsetG, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_B:
FillLookupTableImpl(mAmplitudeB, mExponentB, mOffsetB, aTable);
break;
case B8G8R8A8_COMPONENT_BYTEOFFSET_A:
FillLookupTableImpl(mAmplitudeA, mExponentA, mOffsetA, aTable);
break;
default:
MOZ_ASSERT(false, "unknown component");
break;
}
}
void FilterNodeGammaTransferSoftware::FillLookupTableImpl(Float aAmplitude,
Float aExponent,
Float aOffset,
uint8_t aTable[256]) {
for (size_t i = 0; i < 256; i++) {
int32_t val =
NS_lround(255 * (aAmplitude * pow(i / 255.0f, aExponent) + aOffset));
val = std::min(255, val);
val = std::max(0, val);
aTable[i] = val;
}
}
FilterNodeConvolveMatrixSoftware::FilterNodeConvolveMatrixSoftware()
: mDivisor(0),
mBias(0),
mEdgeMode(EDGE_MODE_DUPLICATE),
mPreserveAlpha(false) {}
int32_t FilterNodeConvolveMatrixSoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_CONVOLVE_MATRIX_IN:
return 0;
default:
return -1;
}
}
void FilterNodeConvolveMatrixSoftware::SetAttribute(
uint32_t aIndex, const IntSize& aKernelSize) {
MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_SIZE);
mKernelSize = aKernelSize;
Invalidate();
}
void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
const Float* aMatrix,
uint32_t aSize) {
MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_MATRIX);
mKernelMatrix = std::vector<Float>(aMatrix, aMatrix + aSize);
Invalidate();
}
void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
Float aValue) {
switch (aIndex) {
case ATT_CONVOLVE_MATRIX_DIVISOR:
mDivisor = aValue;
break;
case ATT_CONVOLVE_MATRIX_BIAS:
mBias = aValue;
break;
default:
MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute");
}
Invalidate();
}
void FilterNodeConvolveMatrixSoftware::SetAttribute(
uint32_t aIndex, const Size& aKernelUnitLength) {
switch (aIndex) {
case ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH:
mKernelUnitLength = aKernelUnitLength;
break;
default:
MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute");
}
Invalidate();
}
void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
const IntPoint& aTarget) {
MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_TARGET);
mTarget = aTarget;
Invalidate();
}
void FilterNodeConvolveMatrixSoftware::SetAttribute(
uint32_t aIndex, const IntRect& aSourceRect) {
MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_SOURCE_RECT);
mSourceRect = aSourceRect;
Invalidate();
}
void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
uint32_t aEdgeMode) {
MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_EDGE_MODE);
mEdgeMode = static_cast<ConvolveMatrixEdgeMode>(aEdgeMode);
Invalidate();
}
void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
bool aPreserveAlpha) {
MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA);
mPreserveAlpha = aPreserveAlpha;
Invalidate();
}
#ifdef DEBUG
static inline void DebugOnlyCheckColorSamplingAccess(
const uint8_t* aSampleAddress, const uint8_t* aBoundsBegin,
const uint8_t* aBoundsEnd) {
MOZ_ASSERT(aSampleAddress >= aBoundsBegin, "accessing before start");
MOZ_ASSERT(aSampleAddress < aBoundsEnd, "accessing after end");
}
#else
# define DebugOnlyCheckColorSamplingAccess(address, boundsBegin, boundsEnd)
#endif
static inline uint8_t ColorComponentAtPoint(const uint8_t* aData,
ptrdiff_t aStride,
const uint8_t* aBoundsBegin,
const uint8_t* aBoundsEnd,
int32_t x, int32_t y, ptrdiff_t bpp,
ptrdiff_t c) {
DebugOnlyCheckColorSamplingAccess(&aData[y * aStride + bpp * x + c],
aBoundsBegin, aBoundsEnd);
return aData[y * aStride + bpp * x + c];
}
static inline int32_t ColorAtPoint(const uint8_t* aData, ptrdiff_t aStride,
const uint8_t* aBoundsBegin,
const uint8_t* aBoundsEnd, int32_t x,
int32_t y) {
DebugOnlyCheckColorSamplingAccess(aData + y * aStride + 4 * x, aBoundsBegin,
aBoundsEnd);
return *(uint32_t*)(aData + y * aStride + 4 * x);
}
// Accepts fractional x & y and does bilinear interpolation.
// Only call this if the pixel (floor(x)+1, floor(y)+1) is accessible.
static inline uint8_t ColorComponentAtPoint(
const uint8_t* aData, ptrdiff_t aStride, const uint8_t* aBoundsBegin,
const uint8_t* aBoundsEnd, Float x, Float y, ptrdiff_t bpp, ptrdiff_t c) {
const uint32_t f = 256;
const int32_t lx = floor(x);
const int32_t ly = floor(y);
const int32_t tux = uint32_t((x - lx) * f);
const int32_t tlx = f - tux;
const int32_t tuy = uint32_t((y - ly) * f);
const int32_t tly = f - tuy;
const uint8_t& cll = ColorComponentAtPoint(aData, aStride, aBoundsBegin,
aBoundsEnd, lx, ly, bpp, c);
const uint8_t& cul = ColorComponentAtPoint(aData, aStride, aBoundsBegin,
aBoundsEnd, lx + 1, ly, bpp, c);
const uint8_t& clu = ColorComponentAtPoint(aData, aStride, aBoundsBegin,
aBoundsEnd, lx, ly + 1, bpp, c);
const uint8_t& cuu = ColorComponentAtPoint(
aData, aStride, aBoundsBegin, aBoundsEnd, lx + 1, ly + 1, bpp, c);
return ((cll * tlx + cul * tux) * tly + (clu * tlx + cuu * tux) * tuy +
f * f / 2) /
(f * f);
}
static int32_t ClampToNonZero(int32_t a) { return a * (a >= 0); }
template <typename CoordType>
static void ConvolvePixel(const uint8_t* aSourceData, uint8_t* aTargetData,
int32_t aWidth, int32_t aHeight,
int32_t aSourceStride, int32_t aTargetStride,
const uint8_t* aSourceBegin,
const uint8_t* aSourceEnd, int32_t aX, int32_t aY,
const int32_t* aKernel, int32_t aBias, int32_t shiftL,
int32_t shiftR, bool aPreserveAlpha, int32_t aOrderX,
int32_t aOrderY, int32_t aTargetX, int32_t aTargetY,
CoordType aKernelUnitLengthX,
CoordType aKernelUnitLengthY) {
int32_t sum[4] = {0, 0, 0, 0};
int32_t offsets[4] = {
B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G,
B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A};
int32_t channels = aPreserveAlpha ? 3 : 4;
int32_t roundingAddition = shiftL == 0 ? 0 : 1 << (shiftL - 1);
for (int32_t y = 0; y < aOrderY; y++) {
CoordType sampleY = aY + (y - aTargetY) * aKernelUnitLengthY;
for (int32_t x = 0; x < aOrderX; x++) {
CoordType sampleX = aX + (x - aTargetX) * aKernelUnitLengthX;
for (int32_t i = 0; i < channels; i++) {
sum[i] +=
aKernel[aOrderX * y + x] *
ColorComponentAtPoint(aSourceData, aSourceStride, aSourceBegin,
aSourceEnd, sampleX, sampleY, 4, offsets[i]);
}
}
}
for (int32_t i = 0; i < channels; i++) {
int32_t clamped =
umin(ClampToNonZero(sum[i] + aBias), 255 << shiftL >> shiftR);
aTargetData[aY * aTargetStride + 4 * aX + offsets[i]] =
(clamped + roundingAddition) << shiftR >> shiftL;
}
if (aPreserveAlpha) {
aTargetData[aY * aTargetStride + 4 * aX + B8G8R8A8_COMPONENT_BYTEOFFSET_A] =
aSourceData[aY * aSourceStride + 4 * aX +
B8G8R8A8_COMPONENT_BYTEOFFSET_A];
}
}
already_AddRefed<DataSourceSurface> FilterNodeConvolveMatrixSoftware::Render(
const IntRect& aRect) {
if (mKernelUnitLength.width == floor(mKernelUnitLength.width) &&
mKernelUnitLength.height == floor(mKernelUnitLength.height)) {
return DoRender(aRect, (int32_t)mKernelUnitLength.width,
(int32_t)mKernelUnitLength.height);
}
return DoRender(aRect, mKernelUnitLength.width, mKernelUnitLength.height);
}
static std::vector<Float> ReversedVector(const std::vector<Float>& aVector) {
size_t length = aVector.size();
std::vector<Float> result(length, 0);
for (size_t i = 0; i < length; i++) {
result[length - 1 - i] = aVector[i];
}
return result;
}
static std::vector<Float> ScaledVector(const std::vector<Float>& aVector,
Float aDivisor) {
size_t length = aVector.size();
std::vector<Float> result(length, 0);
for (size_t i = 0; i < length; i++) {
result[i] = aVector[i] / aDivisor;
}
return result;
}
static Float MaxVectorSum(const std::vector<Float>& aVector) {
Float sum = 0;
size_t length = aVector.size();
for (size_t i = 0; i < length; i++) {
if (aVector[i] > 0) {
sum += aVector[i];
}
}
return sum;
}
// Returns shiftL and shiftR in such a way that
// a << shiftL >> shiftR is roughly a * aFloat.
static void TranslateDoubleToShifts(double aDouble, int32_t& aShiftL,
int32_t& aShiftR) {
aShiftL = 0;
aShiftR = 0;
if (aDouble <= 0) {
MOZ_CRASH("GFX: TranslateDoubleToShifts");
}
if (aDouble < 1) {
while (1 << (aShiftR + 1) < 1 / aDouble) {
aShiftR++;
}
} else {
while (1 << (aShiftL + 1) < aDouble) {
aShiftL++;
}
}
}
template <typename CoordType>
already_AddRefed<DataSourceSurface> FilterNodeConvolveMatrixSoftware::DoRender(
const IntRect& aRect, CoordType aKernelUnitLengthX,
CoordType aKernelUnitLengthY) {
if (mKernelSize.width <= 0 || mKernelSize.height <= 0 ||
mKernelMatrix.size() !=
uint32_t(mKernelSize.width * mKernelSize.height) ||
!IntRect(IntPoint(0, 0), mKernelSize).Contains(mTarget) ||
mDivisor == 0) {
return Factory::CreateDataSourceSurface(aRect.Size(),
SurfaceFormat::B8G8R8A8, true);
}
IntRect srcRect = InflatedSourceRect(aRect);
// Inflate the source rect by another pixel because the bilinear filtering in
// ColorComponentAtPoint may want to access the margins.
srcRect.Inflate(1);
RefPtr<DataSourceSurface> input =
GetInputDataSourceSurface(IN_CONVOLVE_MATRIX_IN, srcRect,
NEED_COLOR_CHANNELS, mEdgeMode, &mSourceRect);
if (!input) {
return nullptr;
}
RefPtr<DataSourceSurface> target = Factory::CreateDataSourceSurface(
aRect.Size(), SurfaceFormat::B8G8R8A8, true);
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
IntPoint offset = aRect.TopLeft() - srcRect.TopLeft();
DataSourceSurface::ScopedMap sourceMap(input, DataSourceSurface::READ);
DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !targetMap.IsMapped())) {
return nullptr;
}
uint8_t* sourceData =
DataAtOffset(input, sourceMap.GetMappedSurface(), offset);
int32_t sourceStride = sourceMap.GetStride();
uint8_t* sourceBegin = sourceMap.GetData();
uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height;
uint8_t* targetData = targetMap.GetData();
int32_t targetStride = targetMap.GetStride();
// Why exactly are we reversing the kernel?
std::vector<Float> kernel = ReversedVector(mKernelMatrix);
kernel = ScaledVector(kernel, mDivisor);
Float maxResultAbs = std::max(MaxVectorSum(kernel) + mBias,
MaxVectorSum(ScaledVector(kernel, -1)) - mBias);
maxResultAbs = std::max(maxResultAbs, 1.0f);
double idealFactor = INT32_MAX / 2.0 / maxResultAbs / 255.0 * 0.999;
MOZ_ASSERT(255.0 * maxResultAbs * idealFactor <= INT32_MAX / 2.0,
"badly chosen float-to-int scale");
int32_t shiftL, shiftR;
TranslateDoubleToShifts(idealFactor, shiftL, shiftR);
double factorFromShifts = Float(1 << shiftL) / Float(1 << shiftR);
MOZ_ASSERT(255.0 * maxResultAbs * factorFromShifts <= INT32_MAX / 2.0,
"badly chosen float-to-int scale");
int32_t* intKernel = new int32_t[kernel.size()];
for (size_t i = 0; i < kernel.size(); i++) {
intKernel[i] = NS_lround(kernel[i] * factorFromShifts);
}
int32_t bias = NS_lround(mBias * 255 * factorFromShifts);
for (int32_t y = 0; y < aRect.Height(); y++) {
for (int32_t x = 0; x < aRect.Width(); x++) {
ConvolvePixel(sourceData, targetData, aRect.Width(), aRect.Height(),
sourceStride, targetStride, sourceBegin, sourceEnd, x, y,
intKernel, bias, shiftL, shiftR, mPreserveAlpha,
mKernelSize.width, mKernelSize.height, mTarget.x, mTarget.y,
aKernelUnitLengthX, aKernelUnitLengthY);
}
}
delete[] intKernel;
return target.forget();
}
void FilterNodeConvolveMatrixSoftware::RequestFromInputsForRect(
const IntRect& aRect) {
RequestInputRect(IN_CONVOLVE_MATRIX_IN, InflatedSourceRect(aRect));
}
IntRect FilterNodeConvolveMatrixSoftware::MapRectToSource(
const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
return MapInputRectToSource(IN_CONVOLVE_MATRIX_IN, InflatedSourceRect(aRect),
aMax, aSourceNode);
}
IntRect FilterNodeConvolveMatrixSoftware::InflatedSourceRect(
const IntRect& aDestRect) {
if (aDestRect.IsEmpty()) {
return IntRect();
}
IntMargin margin;
margin.left = ceil(mTarget.x * mKernelUnitLength.width);
margin.top = ceil(mTarget.y * mKernelUnitLength.height);
margin.right =
ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width);
margin.bottom =
ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height);
IntRect srcRect = aDestRect;
srcRect.Inflate(margin);
return srcRect;
}
IntRect FilterNodeConvolveMatrixSoftware::InflatedDestRect(
const IntRect& aSourceRect) {
if (aSourceRect.IsEmpty()) {
return IntRect();
}
IntMargin margin;
margin.left =
ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width);
margin.top =
ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height);
margin.right = ceil(mTarget.x * mKernelUnitLength.width);
margin.bottom = ceil(mTarget.y * mKernelUnitLength.height);
IntRect destRect = aSourceRect;
destRect.Inflate(margin);
return destRect;
}
IntRect FilterNodeConvolveMatrixSoftware::GetOutputRectInRect(
const IntRect& aRect) {
IntRect srcRequest = InflatedSourceRect(aRect);
IntRect srcOutput = GetInputRectInRect(IN_COLOR_MATRIX_IN, srcRequest);
return InflatedDestRect(srcOutput).Intersect(aRect);
}
FilterNodeDisplacementMapSoftware::FilterNodeDisplacementMapSoftware()
: mScale(0.0f), mChannelX(COLOR_CHANNEL_R), mChannelY(COLOR_CHANNEL_G) {}
int32_t FilterNodeDisplacementMapSoftware::InputIndex(
uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_DISPLACEMENT_MAP_IN:
return 0;
case IN_DISPLACEMENT_MAP_IN2:
return 1;
default:
return -1;
}
}
void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex,
Float aScale) {
MOZ_ASSERT(aIndex == ATT_DISPLACEMENT_MAP_SCALE);
mScale = aScale;
Invalidate();
}
void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex,
uint32_t aValue) {
switch (aIndex) {
case ATT_DISPLACEMENT_MAP_X_CHANNEL:
mChannelX = static_cast<ColorChannel>(aValue);
break;
case ATT_DISPLACEMENT_MAP_Y_CHANNEL:
mChannelY = static_cast<ColorChannel>(aValue);
break;
default:
MOZ_CRASH("GFX: FilterNodeDisplacementMapSoftware::SetAttribute");
}
Invalidate();
}
already_AddRefed<DataSourceSurface> FilterNodeDisplacementMapSoftware::Render(
const IntRect& aRect) {
IntRect srcRect = InflatedSourceOrDestRect(aRect);
RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(
IN_DISPLACEMENT_MAP_IN, srcRect, NEED_COLOR_CHANNELS);
RefPtr<DataSourceSurface> map = GetInputDataSourceSurface(
IN_DISPLACEMENT_MAP_IN2, aRect, NEED_COLOR_CHANNELS);
RefPtr<DataSourceSurface> target =
Factory::CreateDataSourceSurface(aRect.Size(), SurfaceFormat::B8G8R8A8);
if (MOZ2D_WARN_IF(!(input && map && target))) {
return nullptr;
}
IntPoint offset = aRect.TopLeft() - srcRect.TopLeft();
DataSourceSurface::ScopedMap inputMap(input, DataSourceSurface::READ);
DataSourceSurface::ScopedMap mapMap(map, DataSourceSurface::READ);
DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!(inputMap.IsMapped() && mapMap.IsMapped() &&
targetMap.IsMapped()))) {
return nullptr;
}
uint8_t* sourceData =
DataAtOffset(input, inputMap.GetMappedSurface(), offset);
int32_t sourceStride = inputMap.GetStride();
uint8_t* sourceBegin = inputMap.GetData();
uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height;
uint8_t* mapData = mapMap.GetData();
int32_t mapStride = mapMap.GetStride();
uint8_t* targetData = targetMap.GetData();
int32_t targetStride = targetMap.GetStride();
static const ptrdiff_t channelMap[4] = {
B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G,
B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A};
uint16_t xChannel = channelMap[mChannelX];
uint16_t yChannel = channelMap[mChannelY];
float scaleOver255 = mScale / 255.0f;
float scaleAdjustment = -0.5f * mScale;
for (int32_t y = 0; y < aRect.Height(); y++) {
for (int32_t x = 0; x < aRect.Width(); x++) {
uint32_t mapIndex = y * mapStride + 4 * x;
uint32_t targIndex = y * targetStride + 4 * x;
int32_t sourceX =
x + scaleOver255 * mapData[mapIndex + xChannel] + scaleAdjustment;
int32_t sourceY =
y + scaleOver255 * mapData[mapIndex + yChannel] + scaleAdjustment;
*(uint32_t*)(targetData + targIndex) = ColorAtPoint(
sourceData, sourceStride, sourceBegin, sourceEnd, sourceX, sourceY);
}
// Keep valgrind happy.
PodZero(&targetData[y * targetStride + 4 * aRect.Width()],
targetStride - 4 * aRect.Width());
}
return target.forget();
}
void FilterNodeDisplacementMapSoftware::RequestFromInputsForRect(
const IntRect& aRect) {
RequestInputRect(IN_DISPLACEMENT_MAP_IN, InflatedSourceOrDestRect(aRect));
RequestInputRect(IN_DISPLACEMENT_MAP_IN2, aRect);
}
IntRect FilterNodeDisplacementMapSoftware::MapRectToSource(
const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
IntRect result =
MapInputRectToSource(IN_DISPLACEMENT_MAP_IN,
InflatedSourceOrDestRect(aRect), aMax, aSourceNode);
result.OrWith(
MapInputRectToSource(IN_DISPLACEMENT_MAP_IN2, aRect, aMax, aSourceNode));
return result;
}
IntRect FilterNodeDisplacementMapSoftware::InflatedSourceOrDestRect(
const IntRect& aDestOrSourceRect) {
IntRect sourceOrDestRect = aDestOrSourceRect;
sourceOrDestRect.Inflate(ceil(fabs(mScale) / 2));
return sourceOrDestRect;
}
IntRect FilterNodeDisplacementMapSoftware::GetOutputRectInRect(
const IntRect& aRect) {
IntRect srcRequest = InflatedSourceOrDestRect(aRect);
IntRect srcOutput = GetInputRectInRect(IN_DISPLACEMENT_MAP_IN, srcRequest);
return InflatedSourceOrDestRect(srcOutput).Intersect(aRect);
}
FilterNodeTurbulenceSoftware::FilterNodeTurbulenceSoftware()
: mNumOctaves(0),
mSeed(0),
mStitchable(false),
mType(TURBULENCE_TYPE_TURBULENCE) {}
int32_t FilterNodeTurbulenceSoftware::InputIndex(uint32_t aInputEnumIndex) {
return -1;
}
void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex,
const Size& aBaseFrequency) {
switch (aIndex) {
case ATT_TURBULENCE_BASE_FREQUENCY:
mBaseFrequency = aBaseFrequency;
break;
default:
MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute");
break;
}
Invalidate();
}
void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex,
const IntRect& aRect) {
switch (aIndex) {
case ATT_TURBULENCE_RECT:
mRenderRect = aRect;
break;
default:
MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute");
break;
}
Invalidate();
}
void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex,
bool aStitchable) {
MOZ_ASSERT(aIndex == ATT_TURBULENCE_STITCHABLE);
mStitchable = aStitchable;
Invalidate();
}
void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex,
uint32_t aValue) {
switch (aIndex) {
case ATT_TURBULENCE_NUM_OCTAVES:
mNumOctaves = aValue;
break;
case ATT_TURBULENCE_SEED:
mSeed = aValue;
break;
case ATT_TURBULENCE_TYPE:
mType = static_cast<TurbulenceType>(aValue);
break;
default:
MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute");
break;
}
Invalidate();
}
already_AddRefed<DataSourceSurface> FilterNodeTurbulenceSoftware::Render(
const IntRect& aRect) {
return FilterProcessing::RenderTurbulence(
aRect.Size(), aRect.TopLeft(), mBaseFrequency, mSeed, mNumOctaves, mType,
mStitchable, Rect(mRenderRect));
}
IntRect FilterNodeTurbulenceSoftware::GetOutputRectInRect(
const IntRect& aRect) {
return aRect.Intersect(mRenderRect);
}
IntRect FilterNodeTurbulenceSoftware::MapRectToSource(const IntRect& aRect,
const IntRect& aMax,
FilterNode* aSourceNode) {
return IntRect();
}
FilterNodeArithmeticCombineSoftware::FilterNodeArithmeticCombineSoftware()
: mK1(0), mK2(0), mK3(0), mK4(0) {}
int32_t FilterNodeArithmeticCombineSoftware::InputIndex(
uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_ARITHMETIC_COMBINE_IN:
return 0;
case IN_ARITHMETIC_COMBINE_IN2:
return 1;
default:
return -1;
}
}
void FilterNodeArithmeticCombineSoftware::SetAttribute(uint32_t aIndex,
const Float* aFloat,
uint32_t aSize) {
MOZ_ASSERT(aIndex == ATT_ARITHMETIC_COMBINE_COEFFICIENTS);
MOZ_RELEASE_ASSERT(aSize == 4);
mK1 = aFloat[0];
mK2 = aFloat[1];
mK3 = aFloat[2];
mK4 = aFloat[3];
Invalidate();
}
already_AddRefed<DataSourceSurface> FilterNodeArithmeticCombineSoftware::Render(
const IntRect& aRect) {
RefPtr<DataSourceSurface> input1 = GetInputDataSourceSurface(
IN_ARITHMETIC_COMBINE_IN, aRect, NEED_COLOR_CHANNELS);
RefPtr<DataSourceSurface> input2 = GetInputDataSourceSurface(
IN_ARITHMETIC_COMBINE_IN2, aRect, NEED_COLOR_CHANNELS);
if (!input1 && !input2) {
return nullptr;
}
// If one input is null, treat it as transparent by adjusting the factors.
Float k1 = mK1, k2 = mK2, k3 = mK3, k4 = mK4;
if (!input1) {
k1 = 0.0f;
k2 = 0.0f;
input1 = input2;
}
if (!input2) {
k1 = 0.0f;
k3 = 0.0f;
input2 = input1;
}
return FilterProcessing::ApplyArithmeticCombine(input1, input2, k1, k2, k3,
k4);
}
void FilterNodeArithmeticCombineSoftware::RequestFromInputsForRect(
const IntRect& aRect) {
RequestInputRect(IN_ARITHMETIC_COMBINE_IN, aRect);
RequestInputRect(IN_ARITHMETIC_COMBINE_IN2, aRect);
}
IntRect FilterNodeArithmeticCombineSoftware::MapRectToSource(
const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
IntRect result =
MapInputRectToSource(IN_ARITHMETIC_COMBINE_IN, aRect, aMax, aSourceNode);
result.OrWith(MapInputRectToSource(IN_ARITHMETIC_COMBINE_IN2, aRect, aMax,
aSourceNode));
return result;
}
IntRect FilterNodeArithmeticCombineSoftware::GetOutputRectInRect(
const IntRect& aRect) {
if (mK4 > 0.0f) {
return aRect;
}
IntRect rectFrom1 =
GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN, aRect).Intersect(aRect);
IntRect rectFrom2 =
GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN2, aRect).Intersect(aRect);
IntRect result;
if (mK1 > 0.0f) {
result = rectFrom1.Intersect(rectFrom2);
}
if (mK2 > 0.0f) {
result = result.Union(rectFrom1);
}
if (mK3 > 0.0f) {
result = result.Union(rectFrom2);
}
return result;
}
FilterNodeCompositeSoftware::FilterNodeCompositeSoftware()
: mOperator(COMPOSITE_OPERATOR_OVER) {}
int32_t FilterNodeCompositeSoftware::InputIndex(uint32_t aInputEnumIndex) {
return aInputEnumIndex - IN_COMPOSITE_IN_START;
}
void FilterNodeCompositeSoftware::SetAttribute(uint32_t aIndex,
uint32_t aCompositeOperator) {
MOZ_ASSERT(aIndex == ATT_COMPOSITE_OPERATOR);
mOperator = static_cast<CompositeOperator>(aCompositeOperator);
Invalidate();
}
already_AddRefed<DataSourceSurface> FilterNodeCompositeSoftware::Render(
const IntRect& aRect) {
RefPtr<DataSourceSurface> start = GetInputDataSourceSurface(
IN_COMPOSITE_IN_START, aRect, NEED_COLOR_CHANNELS);
RefPtr<DataSourceSurface> dest = Factory::CreateDataSourceSurface(
aRect.Size(), SurfaceFormat::B8G8R8A8, true);
if (MOZ2D_WARN_IF(!dest)) {
return nullptr;
}
if (start) {
CopyRect(start, dest, aRect - aRect.TopLeft(), IntPoint());
}
for (size_t inputIndex = 1; inputIndex < NumberOfSetInputs(); inputIndex++) {
RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(
IN_COMPOSITE_IN_START + inputIndex, aRect, NEED_COLOR_CHANNELS);
if (input) {
FilterProcessing::ApplyComposition(input, dest, mOperator);
} else {
// We need to treat input as transparent. Depending on the composite
// operator, different things happen to dest.
switch (mOperator) {
case COMPOSITE_OPERATOR_OVER:
case COMPOSITE_OPERATOR_ATOP:
case COMPOSITE_OPERATOR_XOR:
case COMPOSITE_OPERATOR_LIGHTER:
// dest is unchanged.
break;
case COMPOSITE_OPERATOR_OUT:
// dest is now transparent, but it can become non-transparent again
// when compositing additional inputs.
ClearDataSourceSurface(dest);
break;
case COMPOSITE_OPERATOR_IN:
// Transparency always wins. We're completely transparent now and
// no additional input can get rid of that transparency.
return nullptr;
}
}
}
return dest.forget();
}
void FilterNodeCompositeSoftware::RequestFromInputsForRect(
const IntRect& aRect) {
for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) {
RequestInputRect(IN_COMPOSITE_IN_START + inputIndex, aRect);
}
}
IntRect FilterNodeCompositeSoftware::MapRectToSource(const IntRect& aRect,
const IntRect& aMax,
FilterNode* aSourceNode) {
IntRect result;
for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) {
result.OrWith(MapInputRectToSource(IN_COMPOSITE_IN_START + inputIndex,
aRect, aMax, aSourceNode));
}
return result;
}
IntRect FilterNodeCompositeSoftware::GetOutputRectInRect(const IntRect& aRect) {
IntRect rect;
for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) {
IntRect inputRect =
GetInputRectInRect(IN_COMPOSITE_IN_START + inputIndex, aRect);
if (mOperator == COMPOSITE_OPERATOR_IN && inputIndex > 0) {
rect = rect.Intersect(inputRect);
} else {
rect = rect.Union(inputRect);
}
}
return rect;
}
int32_t FilterNodeBlurXYSoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_GAUSSIAN_BLUR_IN:
return 0;
default:
return -1;
}
}
already_AddRefed<DataSourceSurface> FilterNodeBlurXYSoftware::Render(
const IntRect& aRect) {
Size sigmaXY = StdDeviationXY();
IntSize d =
AlphaBoxBlur::CalculateBlurRadius(Point(sigmaXY.width, sigmaXY.height));
if (d.width == 0 && d.height == 0) {
return GetInputDataSourceSurface(IN_GAUSSIAN_BLUR_IN, aRect);
}
IntRect srcRect = InflatedSourceOrDestRect(aRect);
RefPtr<DataSourceSurface> input =
GetInputDataSourceSurface(IN_GAUSSIAN_BLUR_IN, srcRect);
if (!input) {
return nullptr;
}
RefPtr<DataSourceSurface> target;
Rect r(0, 0, srcRect.Width(), srcRect.Height());
if (input->GetFormat() == SurfaceFormat::A8) {
target =
Factory::CreateDataSourceSurface(srcRect.Size(), SurfaceFormat::A8);
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
CopyRect(input, target, IntRect(IntPoint(), input->GetSize()), IntPoint());
DataSourceSurface::ScopedMap targetMap(target,
DataSourceSurface::READ_WRITE);
if (MOZ2D_WARN_IF(!targetMap.IsMapped())) {
return nullptr;
}
AlphaBoxBlur blur(r, targetMap.GetStride(), sigmaXY.width, sigmaXY.height);
blur.Blur(targetMap.GetData());
} else {
RefPtr<DataSourceSurface> channel0, channel1, channel2, channel3;
FilterProcessing::SeparateColorChannels(input, channel0, channel1, channel2,
channel3);
if (MOZ2D_WARN_IF(!(channel0 && channel1 && channel2 && channel3))) {
return nullptr;
}
{
DataSourceSurface::ScopedMap channel0Map(channel0,
DataSourceSurface::READ_WRITE);
DataSourceSurface::ScopedMap channel1Map(channel1,
DataSourceSurface::READ_WRITE);
DataSourceSurface::ScopedMap channel2Map(channel2,
DataSourceSurface::READ_WRITE);
DataSourceSurface::ScopedMap channel3Map(channel3,
DataSourceSurface::READ_WRITE);
if (MOZ2D_WARN_IF(!(channel0Map.IsMapped() && channel1Map.IsMapped() &&
channel2Map.IsMapped() && channel3Map.IsMapped()))) {
return nullptr;
}
AlphaBoxBlur blur(r, channel0Map.GetStride(), sigmaXY.width,
sigmaXY.height);
blur.Blur(channel0Map.GetData());
blur.Blur(channel1Map.GetData());
blur.Blur(channel2Map.GetData());
blur.Blur(channel3Map.GetData());
}
target = FilterProcessing::CombineColorChannels(channel0, channel1,
channel2, channel3);
}
return GetDataSurfaceInRect(target, srcRect, aRect, EDGE_MODE_NONE);
}
void FilterNodeBlurXYSoftware::RequestFromInputsForRect(const IntRect& aRect) {
RequestInputRect(IN_GAUSSIAN_BLUR_IN, InflatedSourceOrDestRect(aRect));
}
IntRect FilterNodeBlurXYSoftware::MapRectToSource(const IntRect& aRect,
const IntRect& aMax,
FilterNode* aSourceNode) {
return MapInputRectToSource(
IN_GAUSSIAN_BLUR_IN, InflatedSourceOrDestRect(aRect), aMax, aSourceNode);
}
IntRect FilterNodeBlurXYSoftware::InflatedSourceOrDestRect(
const IntRect& aDestRect) {
Size sigmaXY = StdDeviationXY();
IntSize d =
AlphaBoxBlur::CalculateBlurRadius(Point(sigmaXY.width, sigmaXY.height));
IntRect srcRect = aDestRect;
srcRect.Inflate(d);
return srcRect;
}
IntRect FilterNodeBlurXYSoftware::GetOutputRectInRect(const IntRect& aRect) {
IntRect srcRequest = InflatedSourceOrDestRect(aRect);
IntRect srcOutput = GetInputRectInRect(IN_GAUSSIAN_BLUR_IN, srcRequest);
return InflatedSourceOrDestRect(srcOutput).Intersect(aRect);
}
FilterNodeGaussianBlurSoftware::FilterNodeGaussianBlurSoftware()
: mStdDeviation(0) {}
static float ClampStdDeviation(float aStdDeviation) {
// Cap software blur radius for performance reasons.
return std::min(std::max(0.0f, aStdDeviation), 100.0f);
}
void FilterNodeGaussianBlurSoftware::SetAttribute(uint32_t aIndex,
float aStdDeviation) {
switch (aIndex) {
case ATT_GAUSSIAN_BLUR_STD_DEVIATION:
mStdDeviation = ClampStdDeviation(aStdDeviation);
break;
default:
MOZ_CRASH("GFX: FilterNodeGaussianBlurSoftware::SetAttribute");
}
Invalidate();
}
Size FilterNodeGaussianBlurSoftware::StdDeviationXY() {
return Size(mStdDeviation, mStdDeviation);
}
FilterNodeDirectionalBlurSoftware::FilterNodeDirectionalBlurSoftware()
: mStdDeviation(0.0), mBlurDirection(BLUR_DIRECTION_X) {}
void FilterNodeDirectionalBlurSoftware::SetAttribute(uint32_t aIndex,
Float aStdDeviation) {
switch (aIndex) {
case ATT_DIRECTIONAL_BLUR_STD_DEVIATION:
mStdDeviation = ClampStdDeviation(aStdDeviation);
break;
default:
MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute");
}
Invalidate();
}
void FilterNodeDirectionalBlurSoftware::SetAttribute(uint32_t aIndex,
uint32_t aBlurDirection) {
switch (aIndex) {
case ATT_DIRECTIONAL_BLUR_DIRECTION:
mBlurDirection = (BlurDirection)aBlurDirection;
break;
default:
MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute");
}
Invalidate();
}
Size FilterNodeDirectionalBlurSoftware::StdDeviationXY() {
float sigmaX = mBlurDirection == BLUR_DIRECTION_X ? mStdDeviation : 0;
float sigmaY = mBlurDirection == BLUR_DIRECTION_Y ? mStdDeviation : 0;
return Size(sigmaX, sigmaY);
}
int32_t FilterNodeCropSoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_CROP_IN:
return 0;
default:
return -1;
}
}
void FilterNodeCropSoftware::SetAttribute(uint32_t aIndex,
const Rect& aSourceRect) {
MOZ_ASSERT(aIndex == ATT_CROP_RECT);
Rect srcRect = aSourceRect;
srcRect.Round();
if (!srcRect.ToIntRect(&mCropRect)) {
mCropRect = IntRect();
}
Invalidate();
}
already_AddRefed<DataSourceSurface> FilterNodeCropSoftware::Render(
const IntRect& aRect) {
return GetInputDataSourceSurface(IN_CROP_IN, aRect.Intersect(mCropRect));
}
void FilterNodeCropSoftware::RequestFromInputsForRect(const IntRect& aRect) {
RequestInputRect(IN_CROP_IN, aRect.Intersect(mCropRect));
}
IntRect FilterNodeCropSoftware::MapRectToSource(const IntRect& aRect,
const IntRect& aMax,
FilterNode* aSourceNode) {
return MapInputRectToSource(IN_CROP_IN, aRect.Intersect(mCropRect), aMax,
aSourceNode);
}
IntRect FilterNodeCropSoftware::GetOutputRectInRect(const IntRect& aRect) {
return GetInputRectInRect(IN_CROP_IN, aRect).Intersect(mCropRect);
}
int32_t FilterNodePremultiplySoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_PREMULTIPLY_IN:
return 0;
default:
return -1;
}
}
already_AddRefed<DataSourceSurface> FilterNodePremultiplySoftware::Render(
const IntRect& aRect) {
RefPtr<DataSourceSurface> input =
GetInputDataSourceSurface(IN_PREMULTIPLY_IN, aRect);
return input ? Premultiply(input) : nullptr;
}
void FilterNodePremultiplySoftware::RequestFromInputsForRect(
const IntRect& aRect) {
RequestInputRect(IN_PREMULTIPLY_IN, aRect);
}
IntRect FilterNodePremultiplySoftware::MapRectToSource(
const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
return MapInputRectToSource(IN_PREMULTIPLY_IN, aRect, aMax, aSourceNode);
}
IntRect FilterNodePremultiplySoftware::GetOutputRectInRect(
const IntRect& aRect) {
return GetInputRectInRect(IN_PREMULTIPLY_IN, aRect);
}
int32_t FilterNodeUnpremultiplySoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_UNPREMULTIPLY_IN:
return 0;
default:
return -1;
}
}
already_AddRefed<DataSourceSurface> FilterNodeUnpremultiplySoftware::Render(
const IntRect& aRect) {
RefPtr<DataSourceSurface> input =
GetInputDataSourceSurface(IN_UNPREMULTIPLY_IN, aRect);
return input ? Unpremultiply(input) : nullptr;
}
void FilterNodeUnpremultiplySoftware::RequestFromInputsForRect(
const IntRect& aRect) {
RequestInputRect(IN_UNPREMULTIPLY_IN, aRect);
}
IntRect FilterNodeUnpremultiplySoftware::MapRectToSource(
const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
return MapInputRectToSource(IN_UNPREMULTIPLY_IN, aRect, aMax, aSourceNode);
}
IntRect FilterNodeUnpremultiplySoftware::GetOutputRectInRect(
const IntRect& aRect) {
return GetInputRectInRect(IN_UNPREMULTIPLY_IN, aRect);
}
void FilterNodeOpacitySoftware::SetAttribute(uint32_t aIndex, Float aValue) {
MOZ_ASSERT(aIndex == ATT_OPACITY_VALUE);
mValue = aValue;
Invalidate();
}
int32_t FilterNodeOpacitySoftware::InputIndex(uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_OPACITY_IN:
return 0;
default:
return -1;
}
}
already_AddRefed<DataSourceSurface> FilterNodeOpacitySoftware::Render(
const IntRect& aRect) {
RefPtr<DataSourceSurface> input =
GetInputDataSourceSurface(IN_OPACITY_IN, aRect);
return input ? Opacity(input, mValue) : nullptr;
}
void FilterNodeOpacitySoftware::RequestFromInputsForRect(const IntRect& aRect) {
RequestInputRect(IN_OPACITY_IN, aRect);
}
IntRect FilterNodeOpacitySoftware::MapRectToSource(const IntRect& aRect,
const IntRect& aMax,
FilterNode* aSourceNode) {
return MapInputRectToSource(IN_OPACITY_IN, aRect, aMax, aSourceNode);
}
IntRect FilterNodeOpacitySoftware::GetOutputRectInRect(const IntRect& aRect) {
return GetInputRectInRect(IN_OPACITY_IN, aRect);
}
bool PointLightSoftware::SetAttribute(uint32_t aIndex, const Point3D& aPoint) {
switch (aIndex) {
case ATT_POINT_LIGHT_POSITION:
mPosition = aPoint;
break;
default:
return false;
}
return true;
}
SpotLightSoftware::SpotLightSoftware()
: mSpecularFocus(0), mLimitingConeAngle(0), mLimitingConeCos(1) {}
bool SpotLightSoftware::SetAttribute(uint32_t aIndex, const Point3D& aPoint) {
switch (aIndex) {
case ATT_SPOT_LIGHT_POSITION:
mPosition = aPoint;
break;
case ATT_SPOT_LIGHT_POINTS_AT:
mPointsAt = aPoint;
break;
default:
return false;
}
return true;
}
bool SpotLightSoftware::SetAttribute(uint32_t aIndex, Float aValue) {
switch (aIndex) {
case ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE:
mLimitingConeAngle = aValue;
break;
case ATT_SPOT_LIGHT_FOCUS:
mSpecularFocus = aValue;
break;
default:
return false;
}
return true;
}
DistantLightSoftware::DistantLightSoftware() : mAzimuth(0), mElevation(0) {}
bool DistantLightSoftware::SetAttribute(uint32_t aIndex, Float aValue) {
switch (aIndex) {
case ATT_DISTANT_LIGHT_AZIMUTH:
mAzimuth = aValue;
break;
case ATT_DISTANT_LIGHT_ELEVATION:
mElevation = aValue;
break;
default:
return false;
}
return true;
}
static inline Point3D Normalized(const Point3D& vec) {
Point3D copy(vec);
copy.Normalize();
return copy;
}
template <typename LightType, typename LightingType>
FilterNodeLightingSoftware<LightType, LightingType>::FilterNodeLightingSoftware(
const char* aTypeName)
: mLock("FilterNodeLightingSoftware"),
mSurfaceScale(0)
#if defined(MOZILLA_INTERNAL_API) && \
(defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING))
,
mTypeName(aTypeName)
#endif
{
}
template <typename LightType, typename LightingType>
int32_t FilterNodeLightingSoftware<LightType, LightingType>::InputIndex(
uint32_t aInputEnumIndex) {
switch (aInputEnumIndex) {
case IN_LIGHTING_IN:
return 0;
default:
return -1;
}
}
template <typename LightType, typename LightingType>
void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
uint32_t aIndex, const Point3D& aPoint) {
if (mLight.SetAttribute(aIndex, aPoint)) {
Invalidate();
return;
}
MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute point");
}
template <typename LightType, typename LightingType>
void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
uint32_t aIndex, Float aValue) {
if (mLight.SetAttribute(aIndex, aValue) ||
mLighting.SetAttribute(aIndex, aValue)) {
Invalidate();
return;
}
switch (aIndex) {
case ATT_LIGHTING_SURFACE_SCALE:
mSurfaceScale = std::fpclassify(aValue) == FP_SUBNORMAL ? 0.0 : aValue;
break;
default:
MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute float");
}
Invalidate();
}
template <typename LightType, typename LightingType>
void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
uint32_t aIndex, const Size& aKernelUnitLength) {
switch (aIndex) {
case ATT_LIGHTING_KERNEL_UNIT_LENGTH:
mKernelUnitLength = aKernelUnitLength;
break;
default:
MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute size");
}
Invalidate();
}
template <typename LightType, typename LightingType>
void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(
uint32_t aIndex, const DeviceColor& aColor) {
MOZ_ASSERT(aIndex == ATT_LIGHTING_COLOR);
mColor = aColor;
Invalidate();
}
template <typename LightType, typename LightingType>
IntRect
FilterNodeLightingSoftware<LightType, LightingType>::GetOutputRectInRect(
const IntRect& aRect) {
return aRect;
}
Point3D PointLightSoftware::GetVectorToLight(const Point3D& aTargetPoint) {
return Normalized(mPosition - aTargetPoint);
}
uint32_t PointLightSoftware::GetColor(uint32_t aLightColor,
const Point3D& aVectorToLight) {
return aLightColor;
}
void SpotLightSoftware::Prepare() {
mVectorFromFocusPointToLight = Normalized(mPointsAt - mPosition);
mLimitingConeCos =
std::max<double>(cos(mLimitingConeAngle * M_PI / 180.0), 0.0);
mPowCache.CacheForExponent(mSpecularFocus);
}
Point3D SpotLightSoftware::GetVectorToLight(const Point3D& aTargetPoint) {
return Normalized(mPosition - aTargetPoint);
}
uint32_t SpotLightSoftware::GetColor(uint32_t aLightColor,
const Point3D& aVectorToLight) {
union {
uint32_t color;
uint8_t colorC[4];
};
Float dot = -aVectorToLight.DotProduct(mVectorFromFocusPointToLight);
if (!mPowCache.HasPowerTable()) {
dot *= (dot >= mLimitingConeCos);
color = aLightColor;
colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_R] *= dot;
colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_G] *= dot;
colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_B] *= dot;
} else {
color = aLightColor;
uint16_t doti = dot * (dot >= 0) * (1 << PowCache::sInputIntPrecisionBits);
uint32_t tmp = mPowCache.Pow(doti) * (dot >= mLimitingConeCos);
MOZ_ASSERT(tmp <= (1 << PowCache::sOutputIntPrecisionBits),
"pow() result must not exceed 1.0");
colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_R] =
uint8_t((colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_R] * tmp) >>
PowCache::sOutputIntPrecisionBits);
colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_G] =
uint8_t((colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_G] * tmp) >>
PowCache::sOutputIntPrecisionBits);
colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_B] =
uint8_t((colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_B] * tmp) >>
PowCache::sOutputIntPrecisionBits);
}
colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = 255;
return color;
}
void DistantLightSoftware::Prepare() {
const double radPerDeg = M_PI / 180.0;
mVectorToLight.x = cos(mAzimuth * radPerDeg) * cos(mElevation * radPerDeg);
mVectorToLight.y = sin(mAzimuth * radPerDeg) * cos(mElevation * radPerDeg);
mVectorToLight.z = sin(mElevation * radPerDeg);
}
Point3D DistantLightSoftware::GetVectorToLight(const Point3D& aTargetPoint) {
return mVectorToLight;
}
uint32_t DistantLightSoftware::GetColor(uint32_t aLightColor,
const Point3D& aVectorToLight) {
return aLightColor;
}
template <typename CoordType>
static Point3D GenerateNormal(const uint8_t* data, int32_t stride,
uint8_t* boundsBegin, uint8_t* boundsEnd,
int32_t x, int32_t y, float surfaceScale,
CoordType dx, CoordType dy) {
const uint8_t* index = data + y * stride + x;
CoordType zero = 0;
// See this for source of constants:
// http://www.w3.org/TR/SVG11/filters.html#feDiffuseLightingElement
int16_t normalX = -1 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, -dx, -dy, 1, 0) +
1 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, dx, -dy, 1, 0) +
-2 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, -dx, zero, 1, 0) +
2 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, dx, zero, 1, 0) +
-1 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, -dx, dy, 1, 0) +
1 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, dx, dy, 1, 0);
int16_t normalY = -1 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, -dx, -dy, 1, 0) +
-2 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, zero, -dy, 1, 0) +
-1 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, dx, -dy, 1, 0) +
1 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, -dx, dy, 1, 0) +
2 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, zero, dy, 1, 0) +
1 * ColorComponentAtPoint(index, stride, boundsBegin,
boundsEnd, dx, dy, 1, 0);
Point3D normal;
normal.x = -surfaceScale * normalX / 4.0f;
normal.y = -surfaceScale * normalY / 4.0f;
normal.z = 255;
return Normalized(normal);
}
template <typename LightType, typename LightingType>
already_AddRefed<DataSourceSurface>
FilterNodeLightingSoftware<LightType, LightingType>::Render(
const IntRect& aRect) {
if (mKernelUnitLength.width == floor(mKernelUnitLength.width) &&
mKernelUnitLength.height == floor(mKernelUnitLength.height)) {
return DoRender(aRect, (int32_t)mKernelUnitLength.width,
(int32_t)mKernelUnitLength.height);
}
return DoRender(aRect, mKernelUnitLength.width, mKernelUnitLength.height);
}
template <typename LightType, typename LightingType>
void FilterNodeLightingSoftware<
LightType, LightingType>::RequestFromInputsForRect(const IntRect& aRect) {
IntRect srcRect = aRect;
srcRect.Inflate(ceil(mKernelUnitLength.width),
ceil(mKernelUnitLength.height));
RequestInputRect(IN_LIGHTING_IN, srcRect);
}
template <typename LightType, typename LightingType>
IntRect FilterNodeLightingSoftware<LightType, LightingType>::MapRectToSource(
const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) {
IntRect srcRect = aRect;
srcRect.Inflate(ceil(mKernelUnitLength.width),
ceil(mKernelUnitLength.height));
return MapInputRectToSource(IN_LIGHTING_IN, srcRect, aMax, aSourceNode);
}
template <typename LightType, typename LightingType>
template <typename CoordType>
already_AddRefed<DataSourceSurface>
FilterNodeLightingSoftware<LightType, LightingType>::DoRender(
const IntRect& aRect, CoordType aKernelUnitLengthX,
CoordType aKernelUnitLengthY) {
MOZ_ASSERT(aKernelUnitLengthX > 0,
"aKernelUnitLengthX can be a negative or zero value");
MOZ_ASSERT(aKernelUnitLengthY > 0,
"aKernelUnitLengthY can be a negative or zero value");
IntRect srcRect = aRect;
IntSize size = aRect.Size();
srcRect.Inflate(ceil(float(aKernelUnitLengthX)),
ceil(float(aKernelUnitLengthY)));
// Inflate the source rect by another pixel because the bilinear filtering in
// ColorComponentAtPoint may want to access the margins.
srcRect.Inflate(1);
RefPtr<DataSourceSurface> input = GetInputDataSourceSurface(
IN_LIGHTING_IN, srcRect, CAN_HANDLE_A8, EDGE_MODE_NONE);
if (!input) {
return nullptr;
}
if (input->GetFormat() != SurfaceFormat::A8) {
input = FilterProcessing::ExtractAlpha(input);
}
RefPtr<DataSourceSurface> target =
Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
if (MOZ2D_WARN_IF(!target)) {
return nullptr;
}
IntPoint offset = aRect.TopLeft() - srcRect.TopLeft();
DataSourceSurface::ScopedMap sourceMap(input, DataSourceSurface::READ);
DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE);
if (MOZ2D_WARN_IF(!(sourceMap.IsMapped() && targetMap.IsMapped()))) {
return nullptr;
}
uint8_t* sourceData =
DataAtOffset(input, sourceMap.GetMappedSurface(), offset);
int32_t sourceStride = sourceMap.GetStride();
uint8_t* sourceBegin = sourceMap.GetData();
uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height;
uint8_t* targetData = targetMap.GetData();
int32_t targetStride = targetMap.GetStride();
MutexAutoLock lock(mLock);
uint32_t lightColor = ColorToBGRA(mColor);
mLight.Prepare();
mLighting.Prepare();
for (int32_t y = 0; y < size.height; y++) {
for (int32_t x = 0; x < size.width; x++) {
int32_t sourceIndex = y * sourceStride + x;
int32_t targetIndex = y * targetStride + 4 * x;
Point3D normal =
GenerateNormal(sourceData, sourceStride, sourceBegin, sourceEnd, x, y,
mSurfaceScale, aKernelUnitLengthX, aKernelUnitLengthY);
IntPoint pointInFilterSpace(aRect.X() + x, aRect.Y() + y);
Float Z = mSurfaceScale * sourceData[sourceIndex] / 255.0f;
Point3D pt(pointInFilterSpace.x, pointInFilterSpace.y, Z);
Point3D rayDir = mLight.GetVectorToLight(pt);
uint32_t color = mLight.GetColor(lightColor, rayDir);
*(uint32_t*)(targetData + targetIndex) =
mLighting.LightPixel(normal, rayDir, color);
}
// Zero padding to keep valgrind happy.
PodZero(&targetData[y * targetStride + 4 * size.width],
targetStride - 4 * size.width);
}
return target.forget();
}
DiffuseLightingSoftware::DiffuseLightingSoftware() : mDiffuseConstant(0) {}
bool DiffuseLightingSoftware::SetAttribute(uint32_t aIndex, Float aValue) {
switch (aIndex) {
case ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT:
mDiffuseConstant = aValue;
break;
default:
return false;
}
return true;
}
uint32_t DiffuseLightingSoftware::LightPixel(const Point3D& aNormal,
const Point3D& aVectorToLight,
uint32_t aColor) {
Float dotNL = std::max(0.0f, aNormal.DotProduct(aVectorToLight));
Float diffuseNL = mDiffuseConstant * dotNL;
union {
uint32_t bgra;
uint8_t components[4];
} color = {aColor};
color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = umin(
uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B]),
255U);
color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = umin(
uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G]),
255U);
color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = umin(
uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]),
255U);
color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = 255;
return color.bgra;
}
SpecularLightingSoftware::SpecularLightingSoftware()
: mSpecularConstant(0), mSpecularExponent(0), mSpecularConstantInt(0) {}
bool SpecularLightingSoftware::SetAttribute(uint32_t aIndex, Float aValue) {
switch (aIndex) {
case ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT:
mSpecularConstant = std::min(std::max(aValue, 0.0f), 255.0f);
break;
case ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT:
mSpecularExponent = std::min(std::max(aValue, 1.0f), 128.0f);
break;
default:
return false;
}
return true;
}
void SpecularLightingSoftware::Prepare() {
mPowCache.CacheForExponent(mSpecularExponent);
mSpecularConstantInt = uint32_t(mSpecularConstant * (1 << 8));
}
uint32_t SpecularLightingSoftware::LightPixel(const Point3D& aNormal,
const Point3D& aVectorToLight,
uint32_t aColor) {
Point3D vectorToEye(0, 0, 1);
Point3D halfwayVector = Normalized(aVectorToLight + vectorToEye);
Float dotNH = aNormal.DotProduct(halfwayVector);
uint16_t dotNHi =
uint16_t(dotNH * (dotNH >= 0) * (1 << PowCache::sInputIntPrecisionBits));
// The exponent for specular is in [1,128] range, so we don't need to check
// and optimize for the "default power table" scenario here.
MOZ_ASSERT(mPowCache.HasPowerTable());
uint32_t specularNHi =
uint32_t(mSpecularConstantInt) * mPowCache.Pow(dotNHi) >> 8;
union {
uint32_t bgra;
uint8_t components[4];
} color = {aColor};
color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] =
umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B]) >>
PowCache::sOutputIntPrecisionBits,
255U);
color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] =
umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G]) >>
PowCache::sOutputIntPrecisionBits,
255U);
color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] =
umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]) >>
PowCache::sOutputIntPrecisionBits,
255U);
color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] =
umax(color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B],
umax(color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G],
color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]));
return color.bgra;
}
} // namespace gfx
} // namespace mozilla