diff options
Diffstat (limited to 'gfx/2d/FilterNodeSoftware.cpp')
-rw-r--r-- | gfx/2d/FilterNodeSoftware.cpp | 3750 |
1 files changed, 3750 insertions, 0 deletions
diff --git a/gfx/2d/FilterNodeSoftware.cpp b/gfx/2d/FilterNodeSoftware.cpp new file mode 100644 index 0000000000..0453d86869 --- /dev/null +++ b/gfx/2d/FilterNodeSoftware.cpp @@ -0,0 +1,3750 @@ +/* -*- 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; + + // Retrieve a cached surface if we have one and it can + // satisfy this request, or else request a rect we will compute and cache + if (!mCachedRect.Contains(aRect)) { + RequestRect(aRect); + requestedRect = mRequestedRect; + } else { + MOZ_ASSERT(mCachedOutput, "cached rect but no cached output?"); + cachedRect = mCachedRect; + cachedOutput = mCachedOutput; + } + + if (!cachedOutput) { + // Compute the output + cachedOutput = Render(requestedRect); + + // Update the cache for future requests + mCachedOutput = cachedOutput; + if (!mCachedOutput) { + mCachedRect = IntRect(); + mRequestedRect = IntRect(); + return nullptr; + } + mCachedRect = requestedRect; + mRequestedRect = IntRect(); + + cachedRect = mCachedRect; + } + + return GetDataSurfaceInRect(cachedOutput, cachedRect, aRect, EDGE_MODE_NONE); +} + +void FilterNodeSoftware::RequestRect(const IntRect& aRect) { + if (mRequestedRect.Contains(aRect)) { + // Bail out now. Otherwise pathological filters can spend time exponential + // in the number of primitives, e.g. if each primitive takes the + // previous primitive as its two inputs. + return; + } + mRequestedRect = mRequestedRect.Union(aRect); + RequestFromInputsForRect(aRect); +} + +IntRect FilterNodeSoftware::MapInputRectToSource(uint32_t aInputEnumIndex, + const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0) { + gfxDevCrash(LogReason::FilterInputError) + << "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs(); + return aMax; + } + if ((uint32_t)inputIndex < NumberOfSetInputs()) { + RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex]; + // If we have any input filters call into them to do the mapping, + // otherwise we can assume an input surface will be used + // and just return aRect. + if (filter) { + return filter->MapRectToSource(aRect, aMax, aSourceNode); + } + } + // We have an input surface instead of a filter + // so check if we're the target node. + if (this == aSourceNode) { + return aRect; + } + return IntRect(); +} + +void FilterNodeSoftware::RequestInputRect(uint32_t aInputEnumIndex, + const IntRect& aRect) { + if (aRect.Overflows()) { + return; + } + + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) { + gfxDevCrash(LogReason::FilterInputError) + << "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs(); + return; + } + if (mInputSurfaces[inputIndex]) { + return; + } + RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex]; + MOZ_ASSERT(filter, "missing input"); + + filter->RequestRect(filter->GetOutputRectInRect(aRect)); +} + +SurfaceFormat FilterNodeSoftware::DesiredFormat(SurfaceFormat aCurrentFormat, + FormatHint aFormatHint) { + if (aCurrentFormat == SurfaceFormat::A8 && aFormatHint == CAN_HANDLE_A8) { + return SurfaceFormat::A8; + } + return SurfaceFormat::B8G8R8A8; +} + +already_AddRefed<DataSourceSurface> +FilterNodeSoftware::GetInputDataSourceSurface( + uint32_t aInputEnumIndex, const IntRect& aRect, FormatHint aFormatHint, + ConvolveMatrixEdgeMode aEdgeMode, + const IntRect* aTransparencyPaddedSourceRect) { + if (aRect.Overflows()) { + return nullptr; + } + +#ifdef DEBUG_DUMP_SURFACES + printf( + "<section><h1>GetInputDataSourceSurface with aRect: %d, %d, %d, " + "%d</h1>\n", + aRect.x, aRect.y, aRect.Width(), aRect.Height()); +#endif + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) { + gfxDevCrash(LogReason::FilterInputData) + << "Invalid data " << inputIndex << " vs. " << NumberOfSetInputs(); + return nullptr; + } + + if (aRect.IsEmpty()) { + return nullptr; + } + + RefPtr<SourceSurface> surface; + IntRect surfaceRect; + + if (mInputSurfaces[inputIndex]) { + // Input from input surface + surface = mInputSurfaces[inputIndex]; +#ifdef DEBUG_DUMP_SURFACES + printf("input from input surface:\n"); +#endif + surfaceRect = surface->GetRect(); + } else { + // Input from input filter +#ifdef DEBUG_DUMP_SURFACES + printf("getting input from input filter %s...\n", + mInputFilters[inputIndex]->GetName()); +#endif + RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex]; + MOZ_ASSERT(filter, "missing input"); + IntRect inputFilterOutput = filter->GetOutputRectInRect(aRect); + if (!inputFilterOutput.IsEmpty()) { + surface = filter->GetOutput(inputFilterOutput); + } +#ifdef DEBUG_DUMP_SURFACES + printf("input from input filter %s:\n", + mInputFilters[inputIndex]->GetName()); +#endif + surfaceRect = inputFilterOutput; + MOZ_ASSERT(!surface || surfaceRect.Size() == surface->GetSize()); + } + + if (surface && surface->GetFormat() == SurfaceFormat::UNKNOWN) { +#ifdef DEBUG_DUMP_SURFACES + printf("wrong input format</section>\n\n"); +#endif + return nullptr; + } + + if (!surfaceRect.IsEmpty() && !surface) { +#ifdef DEBUG_DUMP_SURFACES + printf(" -- no input --</section>\n\n"); +#endif + return nullptr; + } + + if (aTransparencyPaddedSourceRect && + !aTransparencyPaddedSourceRect->IsEmpty()) { + IntRect srcRect = aTransparencyPaddedSourceRect->Intersect(aRect); + surface = + GetDataSurfaceInRect(surface, surfaceRect, srcRect, EDGE_MODE_NONE); + if (surface) { + surfaceRect = srcRect; + } else { + // Padding the surface with transparency failed, probably due to size + // restrictions. Since |surface| is now null, set the surfaceRect to + // empty so that we're consistent. + surfaceRect.SetEmpty(); + } + } + + RefPtr<DataSourceSurface> result = + GetDataSurfaceInRect(surface, surfaceRect, aRect, aEdgeMode); + + if (result) { + // TODO: This isn't safe since we don't have a guarantee + // that future Maps will have the same stride + DataSourceSurface::MappedSurface map; + if (result->Map(DataSourceSurface::READ, &map)) { + // Unmap immediately since CloneAligned hasn't been updated + // to use the Map API yet. We can still read the stride/data + // values as long as we don't try to dereference them. + result->Unmap(); + if (map.mStride != GetAlignedStride<16>(map.mStride, 1) || + reinterpret_cast<uintptr_t>(map.mData) % 16 != 0) { + // Align unaligned surface. + result = CloneAligned(result); + } + } else { + result = nullptr; + } + } + + if (!result) { +#ifdef DEBUG_DUMP_SURFACES + printf(" -- no input --</section>\n\n"); +#endif + return nullptr; + } + + SurfaceFormat currentFormat = result->GetFormat(); + if (DesiredFormat(currentFormat, aFormatHint) == SurfaceFormat::B8G8R8A8 && + currentFormat != SurfaceFormat::B8G8R8A8) { + result = FilterProcessing::ConvertToB8G8R8A8(result); + } + +#ifdef DEBUG_DUMP_SURFACES + printf("<img src='"); + gfxUtils::DumpAsDataURL(result); + printf("'></section>"); +#endif + + MOZ_ASSERT(!result || result->GetSize() == aRect.Size(), + "wrong surface size"); + + return result.forget(); +} + +IntRect FilterNodeSoftware::GetInputRectInRect(uint32_t aInputEnumIndex, + const IntRect& aInRect) { + if (aInRect.Overflows()) { + return IntRect(); + } + + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) { + gfxDevCrash(LogReason::FilterInputRect) + << "Invalid rect " << inputIndex << " vs. " << NumberOfSetInputs(); + return IntRect(); + } + if (mInputSurfaces[inputIndex]) { + return aInRect.Intersect(mInputSurfaces[inputIndex]->GetRect()); + } + RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex]; + MOZ_ASSERT(filter, "missing input"); + return filter->GetOutputRectInRect(aInRect); +} + +size_t FilterNodeSoftware::NumberOfSetInputs() { + return std::max(mInputSurfaces.size(), mInputFilters.size()); +} + +void FilterNodeSoftware::AddInvalidationListener( + FilterInvalidationListener* aListener) { + MOZ_ASSERT(aListener, "null listener"); + mInvalidationListeners.push_back(aListener); +} + +void FilterNodeSoftware::RemoveInvalidationListener( + FilterInvalidationListener* aListener) { + MOZ_ASSERT(aListener, "null listener"); + std::vector<FilterInvalidationListener*>::iterator it = std::find( + mInvalidationListeners.begin(), mInvalidationListeners.end(), aListener); + mInvalidationListeners.erase(it); +} + +void FilterNodeSoftware::FilterInvalidated(FilterNodeSoftware* aFilter) { + Invalidate(); +} + +void FilterNodeSoftware::Invalidate() { + mCachedOutput = nullptr; + mCachedRect = IntRect(); + for (std::vector<FilterInvalidationListener*>::iterator it = + mInvalidationListeners.begin(); + it != mInvalidationListeners.end(); it++) { + (*it)->FilterInvalidated(this); + } +} + +FilterNodeSoftware::FilterNodeSoftware() {} + +FilterNodeSoftware::~FilterNodeSoftware() { + MOZ_ASSERT( + mInvalidationListeners.empty(), + "All invalidation listeners should have unsubscribed themselves by now!"); + + for (std::vector<RefPtr<FilterNodeSoftware> >::iterator it = + mInputFilters.begin(); + it != mInputFilters.end(); it++) { + if (*it) { + (*it)->RemoveInvalidationListener(this); + } + } +} + +void FilterNodeSoftware::SetInput(uint32_t aIndex, FilterNode* aFilter) { + if (aFilter && aFilter->GetBackendType() != FILTER_BACKEND_SOFTWARE) { + MOZ_ASSERT(false, "can only take software filters as inputs"); + return; + } + SetInput(aIndex, nullptr, static_cast<FilterNodeSoftware*>(aFilter)); +} + +void FilterNodeSoftware::SetInput(uint32_t aIndex, SourceSurface* aSurface) { + SetInput(aIndex, aSurface, nullptr); +} + +void FilterNodeSoftware::SetInput(uint32_t aInputEnumIndex, + SourceSurface* aSurface, + FilterNodeSoftware* aFilter) { + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0) { + gfxDevCrash(LogReason::FilterInputSet) << "Invalid set " << inputIndex; + return; + } + if ((uint32_t)inputIndex >= NumberOfSetInputs()) { + mInputSurfaces.resize(inputIndex + 1); + mInputFilters.resize(inputIndex + 1); + } + mInputSurfaces[inputIndex] = aSurface; + if (mInputFilters[inputIndex]) { + mInputFilters[inputIndex]->RemoveInvalidationListener(this); + } + if (aFilter) { + aFilter->AddInvalidationListener(this); + } + mInputFilters[inputIndex] = aFilter; + if (!aSurface && !aFilter && (size_t)inputIndex == NumberOfSetInputs()) { + mInputSurfaces.resize(inputIndex); + mInputFilters.resize(inputIndex); + } + Invalidate(); +} + +FilterNodeBlendSoftware::FilterNodeBlendSoftware() + : mBlendMode(BLEND_MODE_MULTIPLY) {} + +int32_t FilterNodeBlendSoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_BLEND_IN: + return 0; + case IN_BLEND_IN2: + return 1; + default: + return -1; + } +} + +void FilterNodeBlendSoftware::SetAttribute(uint32_t aIndex, + uint32_t aBlendMode) { + MOZ_ASSERT(aIndex == ATT_BLEND_BLENDMODE); + mBlendMode = static_cast<BlendMode>(aBlendMode); + Invalidate(); +} + +static CompositionOp ToBlendOp(BlendMode aOp) { + switch (aOp) { + case BLEND_MODE_MULTIPLY: + return CompositionOp::OP_MULTIPLY; + case BLEND_MODE_SCREEN: + return CompositionOp::OP_SCREEN; + case BLEND_MODE_OVERLAY: + return CompositionOp::OP_OVERLAY; + case BLEND_MODE_DARKEN: + return CompositionOp::OP_DARKEN; + case BLEND_MODE_LIGHTEN: + return CompositionOp::OP_LIGHTEN; + case BLEND_MODE_COLOR_DODGE: + return CompositionOp::OP_COLOR_DODGE; + case BLEND_MODE_COLOR_BURN: + return CompositionOp::OP_COLOR_BURN; + case BLEND_MODE_HARD_LIGHT: + return CompositionOp::OP_HARD_LIGHT; + case BLEND_MODE_SOFT_LIGHT: + return CompositionOp::OP_SOFT_LIGHT; + case BLEND_MODE_DIFFERENCE: + return CompositionOp::OP_DIFFERENCE; + case BLEND_MODE_EXCLUSION: + return CompositionOp::OP_EXCLUSION; + case BLEND_MODE_HUE: + return CompositionOp::OP_HUE; + case BLEND_MODE_SATURATION: + return CompositionOp::OP_SATURATION; + case BLEND_MODE_COLOR: + return CompositionOp::OP_COLOR; + case BLEND_MODE_LUMINOSITY: + return CompositionOp::OP_LUMINOSITY; + } + + MOZ_ASSERT_UNREACHABLE("Unexpected BlendMode"); + return CompositionOp::OP_OVER; +} + +already_AddRefed<DataSourceSurface> FilterNodeBlendSoftware::Render( + const IntRect& aRect) { + RefPtr<DataSourceSurface> input1 = + GetInputDataSourceSurface(IN_BLEND_IN, aRect, NEED_COLOR_CHANNELS); + RefPtr<DataSourceSurface> input2 = + GetInputDataSourceSurface(IN_BLEND_IN2, aRect, NEED_COLOR_CHANNELS); + + // Null inputs need to be treated as transparent. + + // First case: both are transparent. + if (!input1 && !input2) { + // Then the result is transparent, too. + return nullptr; + } + + // Second case: one of them is transparent. Return the non-transparent one. + if (!input1 || !input2) { + return input1 ? input1.forget() : input2.forget(); + } + + // Third case: both are non-transparent. + // Apply normal filtering. + RefPtr<DataSourceSurface> target = + FilterProcessing::ApplyBlending(input1, input2, mBlendMode); + if (target != nullptr) { + return target.forget(); + } + + IntSize size = input1->GetSize(); + target = Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + CopyRect(input1, target, IntRect(IntPoint(), size), IntPoint()); + + // This needs to stay in scope until the draw target has been flushed. + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::READ_WRITE); + if (MOZ2D_WARN_IF(!targetMap.IsMapped())) { + return nullptr; + } + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + BackendType::SKIA, targetMap.GetData(), target->GetSize(), + targetMap.GetStride(), target->GetFormat()); + + if (!dt) { + gfxWarning() + << "FilterNodeBlendSoftware::Render failed in CreateDrawTargetForData"; + return nullptr; + } + + Rect r(0, 0, size.width, size.height); + dt->DrawSurface(input2, r, r, DrawSurfaceOptions(), + DrawOptions(1.0f, ToBlendOp(mBlendMode))); + dt->Flush(); + return target.forget(); +} + +void FilterNodeBlendSoftware::RequestFromInputsForRect(const IntRect& aRect) { + RequestInputRect(IN_BLEND_IN, aRect); + RequestInputRect(IN_BLEND_IN2, aRect); +} + +IntRect FilterNodeBlendSoftware::MapRectToSource(const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + IntRect result = MapInputRectToSource(IN_BLEND_IN, aRect, aMax, aSourceNode); + result.OrWith(MapInputRectToSource(IN_BLEND_IN2, aRect, aMax, aSourceNode)); + return result; +} + +IntRect FilterNodeBlendSoftware::GetOutputRectInRect(const IntRect& aRect) { + return GetInputRectInRect(IN_BLEND_IN, aRect) + .Union(GetInputRectInRect(IN_BLEND_IN2, aRect)) + .Intersect(aRect); +} + +FilterNodeTransformSoftware::FilterNodeTransformSoftware() + : mSamplingFilter(SamplingFilter::GOOD) {} + +int32_t FilterNodeTransformSoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_TRANSFORM_IN: + return 0; + default: + return -1; + } +} + +void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex, + uint32_t aFilter) { + MOZ_ASSERT(aIndex == ATT_TRANSFORM_FILTER); + mSamplingFilter = static_cast<SamplingFilter>(aFilter); + Invalidate(); +} + +void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex, + const Matrix& aMatrix) { + MOZ_ASSERT(aIndex == ATT_TRANSFORM_MATRIX); + mMatrix = aMatrix; + Invalidate(); +} + +IntRect FilterNodeTransformSoftware::SourceRectForOutputRect( + const IntRect& aRect) { + if (aRect.IsEmpty()) { + return IntRect(); + } + + Matrix inverted(mMatrix); + if (!inverted.Invert()) { + return IntRect(); + } + + Rect neededRect = inverted.TransformBounds(Rect(aRect)); + neededRect.RoundOut(); + IntRect neededIntRect; + if (!neededRect.ToIntRect(&neededIntRect)) { + return IntRect(); + } + return GetInputRectInRect(IN_TRANSFORM_IN, neededIntRect); +} + +IntRect FilterNodeTransformSoftware::MapRectToSource(const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + if (aRect.IsEmpty()) { + return IntRect(); + } + + Matrix inverted(mMatrix); + if (!inverted.Invert()) { + return aMax; + } + + Rect neededRect = inverted.TransformBounds(Rect(aRect)); + neededRect.RoundOut(); + IntRect neededIntRect; + if (!neededRect.ToIntRect(&neededIntRect)) { + return aMax; + } + return MapInputRectToSource(IN_TRANSFORM_IN, neededIntRect, aMax, + aSourceNode); +} + +already_AddRefed<DataSourceSurface> FilterNodeTransformSoftware::Render( + const IntRect& aRect) { + IntRect srcRect = SourceRectForOutputRect(aRect); + + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_TRANSFORM_IN, srcRect); + + if (!input) { + return nullptr; + } + + Matrix transform = Matrix::Translation(srcRect.X(), srcRect.Y()) * mMatrix * + Matrix::Translation(-aRect.X(), -aRect.Y()); + if (transform.IsIdentity() && srcRect.Size() == aRect.Size()) { + return input.forget(); + } + + RefPtr<DataSourceSurface> surf = + Factory::CreateDataSourceSurface(aRect.Size(), input->GetFormat(), true); + + if (!surf) { + return nullptr; + } + + DataSourceSurface::MappedSurface mapping; + if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) { + gfxCriticalError() + << "FilterNodeTransformSoftware::Render failed to map surface"; + return nullptr; + } + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + BackendType::SKIA, mapping.mData, surf->GetSize(), mapping.mStride, + surf->GetFormat()); + if (!dt) { + gfxWarning() << "FilterNodeTransformSoftware::Render failed in " + "CreateDrawTargetForData"; + return nullptr; + } + + Rect r(0, 0, srcRect.Width(), srcRect.Height()); + dt->SetTransform(transform); + dt->DrawSurface(input, r, r, DrawSurfaceOptions(mSamplingFilter)); + + dt->Flush(); + surf->Unmap(); + return surf.forget(); +} + +void FilterNodeTransformSoftware::RequestFromInputsForRect( + const IntRect& aRect) { + RequestInputRect(IN_TRANSFORM_IN, SourceRectForOutputRect(aRect)); +} + +IntRect FilterNodeTransformSoftware::GetOutputRectInRect(const IntRect& aRect) { + IntRect srcRect = SourceRectForOutputRect(aRect); + if (srcRect.IsEmpty()) { + return IntRect(); + } + + Rect outRect = mMatrix.TransformBounds(Rect(srcRect)); + outRect.RoundOut(); + IntRect outIntRect; + if (!outRect.ToIntRect(&outIntRect)) { + return IntRect(); + } + return outIntRect.Intersect(aRect); +} + +FilterNodeMorphologySoftware::FilterNodeMorphologySoftware() + : mOperator(MORPHOLOGY_OPERATOR_ERODE) {} + +int32_t FilterNodeMorphologySoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_MORPHOLOGY_IN: + return 0; + default: + return -1; + } +} + +void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex, + const IntSize& aRadii) { + MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_RADII); + mRadii.width = std::min(std::max(aRadii.width, 0), 100000); + mRadii.height = std::min(std::max(aRadii.height, 0), 100000); + Invalidate(); +} + +void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex, + uint32_t aOperator) { + MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_OPERATOR); + mOperator = static_cast<MorphologyOperator>(aOperator); + Invalidate(); +} + +static already_AddRefed<DataSourceSurface> ApplyMorphology( + const IntRect& aSourceRect, DataSourceSurface* aInput, + const IntRect& aDestRect, int32_t rx, int32_t ry, + MorphologyOperator aOperator) { + IntRect srcRect = aSourceRect - aDestRect.TopLeft(); + IntRect destRect = aDestRect - aDestRect.TopLeft(); + IntRect tmpRect(destRect.X(), srcRect.Y(), destRect.Width(), + srcRect.Height()); +#ifdef DEBUG + IntMargin margin = srcRect - destRect; + MOZ_ASSERT(margin.top >= ry && margin.right >= rx && margin.bottom >= ry && + margin.left >= rx, + "insufficient margin"); +#endif + + RefPtr<DataSourceSurface> tmp; + if (rx == 0) { + tmp = aInput; + } else { + tmp = Factory::CreateDataSourceSurface(tmpRect.Size(), + SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!tmp)) { + return nullptr; + } + + DataSourceSurface::ScopedMap sourceMap(aInput, DataSourceSurface::READ); + DataSourceSurface::ScopedMap tmpMap(tmp, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !tmpMap.IsMapped())) { + return nullptr; + } + uint8_t* sourceData = DataAtOffset(aInput, sourceMap.GetMappedSurface(), + destRect.TopLeft() - srcRect.TopLeft()); + uint8_t* tmpData = DataAtOffset(tmp, tmpMap.GetMappedSurface(), + destRect.TopLeft() - tmpRect.TopLeft()); + + FilterProcessing::ApplyMorphologyHorizontal( + sourceData, sourceMap.GetStride(), tmpData, tmpMap.GetStride(), tmpRect, + rx, aOperator); + } + + RefPtr<DataSourceSurface> dest; + if (ry == 0) { + dest = tmp; + } else { + dest = Factory::CreateDataSourceSurface(destRect.Size(), + SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!dest)) { + return nullptr; + } + + DataSourceSurface::ScopedMap tmpMap(tmp, DataSourceSurface::READ); + DataSourceSurface::ScopedMap destMap(dest, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!tmpMap.IsMapped() || !destMap.IsMapped())) { + return nullptr; + } + int32_t tmpStride = tmpMap.GetStride(); + uint8_t* tmpData = DataAtOffset(tmp, tmpMap.GetMappedSurface(), + destRect.TopLeft() - tmpRect.TopLeft()); + + int32_t destStride = destMap.GetStride(); + uint8_t* destData = destMap.GetData(); + + FilterProcessing::ApplyMorphologyVertical( + tmpData, tmpStride, destData, destStride, destRect, ry, aOperator); + } + + return dest.forget(); +} + +already_AddRefed<DataSourceSurface> FilterNodeMorphologySoftware::Render( + const IntRect& aRect) { + IntRect srcRect = aRect; + srcRect.Inflate(mRadii); + + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_MORPHOLOGY_IN, srcRect, NEED_COLOR_CHANNELS); + if (!input) { + return nullptr; + } + + int32_t rx = mRadii.width; + int32_t ry = mRadii.height; + + if (rx == 0 && ry == 0) { + return input.forget(); + } + + return ApplyMorphology(srcRect, input, aRect, rx, ry, mOperator); +} + +void FilterNodeMorphologySoftware::RequestFromInputsForRect( + const IntRect& aRect) { + IntRect srcRect = aRect; + srcRect.Inflate(mRadii); + RequestInputRect(IN_MORPHOLOGY_IN, srcRect); +} + +IntRect FilterNodeMorphologySoftware::GetOutputRectInRect( + const IntRect& aRect) { + IntRect inflatedSourceRect = aRect; + inflatedSourceRect.Inflate(mRadii); + IntRect inputRect = GetInputRectInRect(IN_MORPHOLOGY_IN, inflatedSourceRect); + if (mOperator == MORPHOLOGY_OPERATOR_ERODE) { + inputRect.Deflate(mRadii); + } else { + inputRect.Inflate(mRadii); + } + return inputRect.Intersect(aRect); +} + +int32_t FilterNodeColorMatrixSoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_COLOR_MATRIX_IN: + return 0; + default: + return -1; + } +} + +void FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex, + const Matrix5x4& aMatrix) { + MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_MATRIX); + mMatrix = aMatrix; + Invalidate(); +} + +void FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex, + uint32_t aAlphaMode) { + MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_ALPHA_MODE); + mAlphaMode = (AlphaMode)aAlphaMode; + Invalidate(); +} + +static already_AddRefed<DataSourceSurface> Premultiply( + DataSourceSurface* aSurface) { + if (aSurface->GetFormat() == SurfaceFormat::A8) { + RefPtr<DataSourceSurface> surface(aSurface); + return surface.forget(); + } + + IntSize size = aSurface->GetSize(); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ); + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) { + return nullptr; + } + + uint8_t* inputData = inputMap.GetData(); + int32_t inputStride = inputMap.GetStride(); + uint8_t* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + FilterProcessing::DoPremultiplicationCalculation( + size, targetData, targetStride, inputData, inputStride); + + return target.forget(); +} + +static already_AddRefed<DataSourceSurface> Unpremultiply( + DataSourceSurface* aSurface) { + if (aSurface->GetFormat() == SurfaceFormat::A8) { + RefPtr<DataSourceSurface> surface(aSurface); + return surface.forget(); + } + + IntSize size = aSurface->GetSize(); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ); + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) { + return nullptr; + } + + uint8_t* inputData = inputMap.GetData(); + int32_t inputStride = inputMap.GetStride(); + uint8_t* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + FilterProcessing::DoUnpremultiplicationCalculation( + size, targetData, targetStride, inputData, inputStride); + + return target.forget(); +} + +static already_AddRefed<DataSourceSurface> Opacity(DataSourceSurface* aSurface, + Float aValue) { + if (aValue == 1.0f) { + RefPtr<DataSourceSurface> surface(aSurface); + return surface.forget(); + } + + IntSize size = aSurface->GetSize(); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(size, aSurface->GetFormat()); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ); + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) { + return nullptr; + } + + uint8_t* inputData = inputMap.GetData(); + int32_t inputStride = inputMap.GetStride(); + uint8_t* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + if (aSurface->GetFormat() == SurfaceFormat::A8) { + FilterProcessing::DoOpacityCalculationA8(size, targetData, targetStride, + inputData, inputStride, aValue); + } else { + MOZ_ASSERT(aSurface->GetFormat() == SurfaceFormat::B8G8R8A8); + FilterProcessing::DoOpacityCalculation(size, targetData, targetStride, + inputData, inputStride, aValue); + } + + return target.forget(); +} + +already_AddRefed<DataSourceSurface> FilterNodeColorMatrixSoftware::Render( + const IntRect& aRect) { + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_COLOR_MATRIX_IN, aRect, NEED_COLOR_CHANNELS); + if (!input) { + return nullptr; + } + + if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) { + input = Unpremultiply(input); + } + + RefPtr<DataSourceSurface> result = + FilterProcessing::ApplyColorMatrix(input, mMatrix); + + if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) { + result = Premultiply(result); + } + + return result.forget(); +} + +void FilterNodeColorMatrixSoftware::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 = static_cast<int32_t>(ceil(mTarget.x * mKernelUnitLength.width)); + margin.top = static_cast<int32_t>(ceil(mTarget.y * mKernelUnitLength.height)); + margin.right = static_cast<int32_t>( + ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width)); + margin.bottom = static_cast<int32_t>( + 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 = static_cast<int32_t>( + ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width)); + margin.top = static_cast<int32_t>( + ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height)); + margin.right = + static_cast<int32_t>(ceil(mTarget.x * mKernelUnitLength.width)); + margin.bottom = + static_cast<int32_t>(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_CONVOLVE_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) + : mSurfaceScale(0) +#if defined(MOZILLA_INTERNAL_API) && defined(NS_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(); + + 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 = aVectorToLight + vectorToEye; + Float halfwayLength = halfwayVector.Length(); + if (halfwayLength > 0) { + halfwayVector /= halfwayLength; + } + 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 |