From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- gfx/2d/FilterNodeSoftware.cpp | 3689 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3689 insertions(+) create mode 100644 gfx/2d/FilterNodeSoftware.cpp (limited to 'gfx/2d/FilterNodeSoftware.cpp') diff --git a/gfx/2d/FilterNodeSoftware.cpp b/gfx/2d/FilterNodeSoftware.cpp new file mode 100644 index 000000000..3abdb7a02 --- /dev/null +++ b/gfx/2d/FilterNodeSoftware.cpp @@ -0,0 +1,3689 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "DataSurfaceHelpers.h" +#include "FilterNodeSoftware.h" +#include "2D.h" +#include "Tools.h" +#include "Blur.h" +#include +#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); +} + +already_AddRefed +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.x = 0; + fill.width = aFromRect.x; + sampleRect.x = fill.XMost(); + sampleRect.width = 1; + break; + case 1: + fill.x = aFromRect.x; + fill.width = aFromRect.width; + sampleRect.x = fill.x; + sampleRect.width = fill.width; + break; + case 2: + fill.x = aFromRect.XMost(); + fill.width = size.width - fill.x; + sampleRect.x = fill.x - 1; + sampleRect.width = 1; + break; + } + if (fill.width <= 0) { + continue; + } + bool xIsMiddle = (ix == 1); + for (int32_t iy = 0; iy < 3; iy++) { + switch (iy) { + case 0: + fill.y = 0; + fill.height = aFromRect.y; + sampleRect.y = fill.YMost(); + sampleRect.height = 1; + break; + case 1: + fill.y = aFromRect.y; + fill.height = aFromRect.height; + sampleRect.y = fill.y; + sampleRect.height = fill.height; + break; + case 2: + fill.y = aFromRect.YMost(); + fill.height = size.height - fill.y; + sampleRect.y = fill.y - 1; + sampleRect.height = 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 +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); + IntRect intersectInSourceSpace = intersect - sourceRect.TopLeft(); + IntRect intersectInDestSpace = intersect - aDestRect.TopLeft(); + SurfaceFormat format = aSurface ? aSurface->GetFormat() : SurfaceFormat(SurfaceFormat::B8G8R8A8); + + RefPtr target = + Factory::CreateDataSourceSurface(aDestRect.Size(), format, true); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + if (!aSurface) { + return target.forget(); + } + + RefPtr 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 +FilterNodeSoftware::Create(FilterType aType) +{ + RefPtr 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::POINT_DIFFUSE: + filter = new FilterNodeLightingSoftware("FilterNodeLightingSoftware"); + break; + case FilterType::POINT_SPECULAR: + filter = new FilterNodeLightingSoftware("FilterNodeLightingSoftware"); + break; + case FilterType::SPOT_DIFFUSE: + filter = new FilterNodeLightingSoftware("FilterNodeLightingSoftware"); + break; + case FilterType::SPOT_SPECULAR: + filter = new FilterNodeLightingSoftware("FilterNodeLightingSoftware"); + break; + case FilterType::DISTANT_DIFFUSE: + filter = new FilterNodeLightingSoftware("FilterNodeLightingSoftware"); + break; + case FilterType::DISTANT_SPECULAR: + filter = new FilterNodeLightingSoftware("FilterNodeLightingSoftware"); + break; + } + return filter.forget(); +} + +void +FilterNodeSoftware::Draw(DrawTarget* aDrawTarget, + const Rect &aSourceRect, + const Point &aDestPoint, + const DrawOptions &aOptions) +{ +#ifdef DEBUG_DUMP_SURFACES + printf("
\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("
\n"); +#endif + return; + } + + IntRect outputRect = GetOutputRectInRect(renderIntRect); + if (outputRect.Overflows()) { +#ifdef DEBUG_DUMP_SURFACES + printf("output rect overflowed, not painting anything\n"); + printf("\n"); +#endif + return; + } + + RefPtr 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("\n"); +#endif + return; + } + +#ifdef DEBUG_DUMP_SURFACES + printf("output from %s:\n", GetName()); + printf("\n"); + printf("\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(Color(0.0, 0.0, 0.0, 1.0)), + result, + Point(outputRect.TopLeft()) + sourceToDestOffset, + aOptions); + aDrawTarget->PopClip(); + } else { + aDrawTarget->DrawSurface(result, renderedDestRect, + renderedSourceRect - Point(outputRect.TopLeft()), + DrawSurfaceOptions(), aOptions); + } +} + +already_AddRefed +FilterNodeSoftware::GetOutput(const IntRect &aRect) +{ + MOZ_ASSERT(GetOutputRectInRect(aRect).Contains(aRect)); + + if (aRect.Overflows()) { + return nullptr; + } + + if (!mCachedRect.Contains(aRect)) { + RequestRect(aRect); + mCachedOutput = Render(mRequestedRect); + if (!mCachedOutput) { + mCachedRect = IntRect(); + mRequestedRect = IntRect(); + return nullptr; + } + mCachedRect = mRequestedRect; + mRequestedRect = IntRect(); + } else { + MOZ_ASSERT(mCachedOutput, "cached rect but no cached output?"); + } + return GetDataSurfaceInRect(mCachedOutput, mCachedRect, 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); +} + +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 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 +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("

GetInputDataSourceSurface with aRect: %d, %d, %d, %d

\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 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 = IntRect(IntPoint(0, 0), surface->GetSize()); + } else { + // Input from input filter +#ifdef DEBUG_DUMP_SURFACES + printf("getting input from input filter %s...\n", mInputFilters[inputIndex]->GetName()); +#endif + RefPtr 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
\n\n"); +#endif + return nullptr; + } + + if (!surfaceRect.IsEmpty() && !surface) { +#ifdef DEBUG_DUMP_SURFACES + printf(" -- no input --\n\n"); +#endif + return nullptr; + } + + if (aTransparencyPaddedSourceRect && !aTransparencyPaddedSourceRect->IsEmpty()) { + IntRect srcRect = aTransparencyPaddedSourceRect->Intersect(aRect); + surface = GetDataSurfaceInRect(surface, surfaceRect, srcRect, EDGE_MODE_NONE); + surfaceRect = srcRect; + } + + RefPtr 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(map.mData) % 16 != 0) { + // Align unaligned surface. + result = CloneAligned(result); + } + } else { + result = nullptr; + } + } + + + if (!result) { +#ifdef DEBUG_DUMP_SURFACES + printf(" -- no input --\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(""); +#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(IntRect(IntPoint(0, 0), + mInputSurfaces[inputIndex]->GetSize())); + } + RefPtr 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::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::iterator it = mInvalidationListeners.begin(); + it != mInvalidationListeners.end(); it++) { + (*it)->FilterInvalidated(this); + } +} + +FilterNodeSoftware::~FilterNodeSoftware() +{ + MOZ_ASSERT(!mInvalidationListeners.size(), + "All invalidation listeners should have unsubscribed themselves by now!"); + + for (std::vector >::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(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(aBlendMode); + Invalidate(); +} + +static CompositionOp ToBlendOp(BlendMode aOp) +{ + switch (aOp) { + case BLEND_MODE_MULTIPLY: + return CompositionOp::OP_MULTIPLY; + case BLEND_MODE_SCREEN: + return CompositionOp::OP_SCREEN; + case BLEND_MODE_OVERLAY: + return CompositionOp::OP_OVERLAY; + case BLEND_MODE_DARKEN: + return CompositionOp::OP_DARKEN; + case BLEND_MODE_LIGHTEN: + return CompositionOp::OP_LIGHTEN; + case BLEND_MODE_COLOR_DODGE: + return CompositionOp::OP_COLOR_DODGE; + case BLEND_MODE_COLOR_BURN: + return CompositionOp::OP_COLOR_BURN; + case BLEND_MODE_HARD_LIGHT: + return CompositionOp::OP_HARD_LIGHT; + case BLEND_MODE_SOFT_LIGHT: + return CompositionOp::OP_SOFT_LIGHT; + case BLEND_MODE_DIFFERENCE: + return CompositionOp::OP_DIFFERENCE; + case BLEND_MODE_EXCLUSION: + return CompositionOp::OP_EXCLUSION; + case BLEND_MODE_HUE: + return CompositionOp::OP_HUE; + case BLEND_MODE_SATURATION: + return CompositionOp::OP_SATURATION; + case BLEND_MODE_COLOR: + return CompositionOp::OP_COLOR; + case BLEND_MODE_LUMINOSITY: + return CompositionOp::OP_LUMINOSITY; + default: + return CompositionOp::OP_OVER; + } + + return CompositionOp::OP_OVER; +} + +already_AddRefed +FilterNodeBlendSoftware::Render(const IntRect& aRect) +{ + RefPtr input1 = + GetInputDataSourceSurface(IN_BLEND_IN, aRect, NEED_COLOR_CHANNELS); + RefPtr 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 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 dt = + Factory::CreateDrawTargetForData(BackendType::CAIRO, + 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::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(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); +} + +already_AddRefed +FilterNodeTransformSoftware::Render(const IntRect& aRect) +{ + IntRect srcRect = SourceRectForOutputRect(aRect); + + RefPtr 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 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 dt = + Factory::CreateDrawTargetForData(BackendType::CAIRO, + 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(aOperator); + Invalidate(); +} + +static already_AddRefed +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 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 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 +FilterNodeMorphologySoftware::Render(const IntRect& aRect) +{ + IntRect srcRect = aRect; + srcRect.Inflate(mRadii); + + RefPtr 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 +Premultiply(DataSourceSurface* aSurface) +{ + if (aSurface->GetFormat() == SurfaceFormat::A8) { + RefPtr surface(aSurface); + return surface.forget(); + } + + IntSize size = aSurface->GetSize(); + RefPtr 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 +Unpremultiply(DataSourceSurface* aSurface) +{ + if (aSurface->GetFormat() == SurfaceFormat::A8) { + RefPtr surface(aSurface); + return surface.forget(); + } + + IntSize size = aSurface->GetSize(); + RefPtr 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(); +} + +already_AddRefed +FilterNodeColorMatrixSoftware::Render(const IntRect& aRect) +{ + RefPtr input = + GetInputDataSourceSurface(IN_COLOR_MATRIX_IN, aRect, NEED_COLOR_CHANNELS); + if (!input) { + return nullptr; + } + + if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) { + input = Unpremultiply(input); + } + + RefPtr 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::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 Color &aColor) +{ + MOZ_ASSERT(aIndex == ATT_FLOOD_COLOR); + mColor = aColor; + Invalidate(); +} + +static uint32_t +ColorToBGRA(const Color& 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(Color aColor) +{ + if (aColor.r == 0 && aColor.g == 0 && aColor.b == 0) { + return SurfaceFormat::A8; + } + return SurfaceFormat::B8G8R8A8; +} + +already_AddRefed +FilterNodeFloodSoftware::Render(const IntRect& aRect) +{ + SurfaceFormat format = FormatForColor(mColor); + RefPtr 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 +FilterNodeFloodSoftware::GetOutput(const IntRect& aRect) +{ + return Render(aRect); +} + +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 = IntRect(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 +FilterNodeTileSoftware::Render(const IntRect& aRect) +{ + if (mSourceRect.IsEmpty()) { + return nullptr; + } + + if (mSourceRect.Contains(aRect)) { + return GetInputDataSourceSurface(IN_TILE_IN, aRect); + } + + RefPtr target; + + typedef std::map, 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 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) { + static uint8_t sIdentityLookupTable[256]; + static bool sInitializedIdentityLookupTable = false; + if (!sInitializedIdentityLookupTable) { + for (int32_t i = 0; i < 256; i++) { + sIdentityLookupTable[i] = i; + } + sInitializedIdentityLookupTable = true; + } + memcpy(aTables[aComponent], sIdentityLookupTable, 256); + } else { + FillLookupTable(aComponent, aTables[aComponent]); + } +} + +template +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); + } +} + +bool +IsAllZero(uint8_t aLookupTable[256]) +{ + for (int32_t i = 0; i < 256; i++) { + if (aLookupTable[i] != 0) { + return false; + } + } + return true; +} + +already_AddRefed +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 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 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::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 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& 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 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& 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) +{} + +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(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(aEdgeMode); + Invalidate(); +} + +void +FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, + bool aPreserveAlpha) +{ + MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA); + mPreserveAlpha = aPreserveAlpha; + Invalidate(); +} + +#ifdef DEBUG +static bool sColorSamplingAccessControlEnabled = false; +static uint8_t* sColorSamplingAccessControlStart = nullptr; +static uint8_t* sColorSamplingAccessControlEnd = nullptr; + +struct DebugOnlyAutoColorSamplingAccessControl +{ + explicit DebugOnlyAutoColorSamplingAccessControl(DataSourceSurface* aSurface) + { + sColorSamplingAccessControlStart = aSurface->GetData(); + sColorSamplingAccessControlEnd = sColorSamplingAccessControlStart + + aSurface->Stride() * aSurface->GetSize().height; + sColorSamplingAccessControlEnabled = true; + } + + ~DebugOnlyAutoColorSamplingAccessControl() + { + sColorSamplingAccessControlEnabled = false; + } +}; + +static inline void +DebugOnlyCheckColorSamplingAccess(const uint8_t* aSampleAddress) +{ + if (sColorSamplingAccessControlEnabled) { + MOZ_ASSERT(aSampleAddress >= sColorSamplingAccessControlStart, "accessing before start"); + MOZ_ASSERT(aSampleAddress < sColorSamplingAccessControlEnd, "accessing after end"); + } +} +#else +typedef DebugOnly DebugOnlyAutoColorSamplingAccessControl; +#define DebugOnlyCheckColorSamplingAccess(address) +#endif + +static inline uint8_t +ColorComponentAtPoint(const uint8_t *aData, int32_t aStride, int32_t x, int32_t y, size_t bpp, ptrdiff_t c) +{ + DebugOnlyCheckColorSamplingAccess(&aData[y * aStride + bpp * x + c]); + return aData[y * aStride + bpp * x + c]; +} + +static inline int32_t +ColorAtPoint(const uint8_t *aData, int32_t aStride, int32_t x, int32_t y) +{ + DebugOnlyCheckColorSamplingAccess(aData + y * aStride + 4 * x); + 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, int32_t aStride, Float x, Float y, size_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, lx, ly, bpp, c); + const uint8_t &cul = ColorComponentAtPoint(aData, aStride, lx + 1, ly, bpp, c); + const uint8_t &clu = ColorComponentAtPoint(aData, aStride, lx, ly + 1, bpp, c); + const uint8_t &cuu = ColorComponentAtPoint(aData, aStride, 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 +static void +ConvolvePixel(const uint8_t *aSourceData, + uint8_t *aTargetData, + int32_t aWidth, int32_t aHeight, + int32_t aSourceStride, int32_t aTargetStride, + 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, + 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 +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 +ReversedVector(const std::vector &aVector) +{ + size_t length = aVector.size(); + std::vector result(length, 0); + for (size_t i = 0; i < length; i++) { + result[length - 1 - i] = aVector[i]; + } + return result; +} + +static std::vector +ScaledVector(const std::vector &aVector, Float aDivisor) +{ + size_t length = aVector.size(); + std::vector result(length, 0); + for (size_t i = 0; i < length; i++) { + result[i] = aVector[i] / aDivisor; + } + return result; +} + +static Float +MaxVectorSum(const std::vector &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 +already_AddRefed +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 input = + GetInputDataSourceSurface(IN_CONVOLVE_MATRIX_IN, srcRect, NEED_COLOR_CHANNELS, mEdgeMode, &mSourceRect); + + if (!input) { + return nullptr; + } + + DebugOnlyAutoColorSamplingAccessControl accessControl(input); + + RefPtr 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* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + // Why exactly are we reversing the kernel? + std::vector 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, + 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::InflatedSourceRect(const IntRect &aDestRect) +{ + if (aDestRect.IsEmpty()) { + return IntRect(); + } + + IntMargin margin; + margin.left = ceil(mTarget.x * mKernelUnitLength.width); + margin.top = ceil(mTarget.y * mKernelUnitLength.height); + margin.right = ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width); + margin.bottom = ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height); + + IntRect srcRect = aDestRect; + srcRect.Inflate(margin); + return srcRect; +} + +IntRect +FilterNodeConvolveMatrixSoftware::InflatedDestRect(const IntRect &aSourceRect) +{ + if (aSourceRect.IsEmpty()) { + return IntRect(); + } + + IntMargin margin; + margin.left = ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width); + margin.top = ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height); + margin.right = ceil(mTarget.x * mKernelUnitLength.width); + margin.bottom = ceil(mTarget.y * mKernelUnitLength.height); + + IntRect destRect = aSourceRect; + destRect.Inflate(margin); + return destRect; +} + +IntRect +FilterNodeConvolveMatrixSoftware::GetOutputRectInRect(const IntRect& aRect) +{ + IntRect srcRequest = InflatedSourceRect(aRect); + IntRect srcOutput = GetInputRectInRect(IN_COLOR_MATRIX_IN, srcRequest); + return InflatedDestRect(srcOutput).Intersect(aRect); +} + +FilterNodeDisplacementMapSoftware::FilterNodeDisplacementMapSoftware() + : mScale(0.0f) + , mChannelX(COLOR_CHANNEL_R) + , mChannelY(COLOR_CHANNEL_G) +{} + +int32_t +FilterNodeDisplacementMapSoftware::InputIndex(uint32_t aInputEnumIndex) +{ + switch (aInputEnumIndex) { + case IN_DISPLACEMENT_MAP_IN: return 0; + case IN_DISPLACEMENT_MAP_IN2: return 1; + default: return -1; + } +} + +void +FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex, + Float aScale) +{ + MOZ_ASSERT(aIndex == ATT_DISPLACEMENT_MAP_SCALE); + mScale = aScale; + Invalidate(); +} + +void +FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex, uint32_t aValue) +{ + switch (aIndex) { + case ATT_DISPLACEMENT_MAP_X_CHANNEL: + mChannelX = static_cast(aValue); + break; + case ATT_DISPLACEMENT_MAP_Y_CHANNEL: + mChannelY = static_cast(aValue); + break; + default: + MOZ_CRASH("GFX: FilterNodeDisplacementMapSoftware::SetAttribute"); + } + Invalidate(); +} + +already_AddRefed +FilterNodeDisplacementMapSoftware::Render(const IntRect& aRect) +{ + IntRect srcRect = InflatedSourceOrDestRect(aRect); + RefPtr input = + GetInputDataSourceSurface(IN_DISPLACEMENT_MAP_IN, srcRect, NEED_COLOR_CHANNELS); + RefPtr map = + GetInputDataSourceSurface(IN_DISPLACEMENT_MAP_IN2, aRect, NEED_COLOR_CHANNELS); + RefPtr 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* 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, 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::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(aValue); + break; + default: + MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute"); + break; + } + Invalidate(); +} + +already_AddRefed +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); +} + +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_ASSERT(aSize == 4); + + mK1 = aFloat[0]; + mK2 = aFloat[1]; + mK3 = aFloat[2]; + mK4 = aFloat[3]; + + Invalidate(); +} + +already_AddRefed +FilterNodeArithmeticCombineSoftware::Render(const IntRect& aRect) +{ + RefPtr input1 = + GetInputDataSourceSurface(IN_ARITHMETIC_COMBINE_IN, aRect, NEED_COLOR_CHANNELS); + RefPtr 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::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(aCompositeOperator); + Invalidate(); +} + +already_AddRefed +FilterNodeCompositeSoftware::Render(const IntRect& aRect) +{ + RefPtr start = + GetInputDataSourceSurface(IN_COMPOSITE_IN_START, aRect, NEED_COLOR_CHANNELS); + RefPtr 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 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: + // 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::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 +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 input = + GetInputDataSourceSurface(IN_GAUSSIAN_BLUR_IN, srcRect); + if (!input) { + return nullptr; + } + + RefPtr 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 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::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() + : 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 +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::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 +FilterNodePremultiplySoftware::Render(const IntRect& aRect) +{ + RefPtr input = + GetInputDataSourceSurface(IN_PREMULTIPLY_IN, aRect); + return input ? Premultiply(input) : nullptr; +} + +void +FilterNodePremultiplySoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_PREMULTIPLY_IN, aRect); +} + +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 +FilterNodeUnpremultiplySoftware::Render(const IntRect& aRect) +{ + RefPtr input = + GetInputDataSourceSurface(IN_UNPREMULTIPLY_IN, aRect); + return input ? Unpremultiply(input) : nullptr; +} + +void +FilterNodeUnpremultiplySoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + RequestInputRect(IN_UNPREMULTIPLY_IN, aRect); +} + +IntRect +FilterNodeUnpremultiplySoftware::GetOutputRectInRect(const IntRect& aRect) +{ + return GetInputRectInRect(IN_UNPREMULTIPLY_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 +FilterNodeLightingSoftware::FilterNodeLightingSoftware(const char* aTypeName) + : mSurfaceScale(0) +#if defined(MOZILLA_INTERNAL_API) && (defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING)) + , mTypeName(aTypeName) +#endif +{} + +template +int32_t +FilterNodeLightingSoftware::InputIndex(uint32_t aInputEnumIndex) +{ + switch (aInputEnumIndex) { + case IN_LIGHTING_IN: return 0; + default: return -1; + } +} + +template +void +FilterNodeLightingSoftware::SetAttribute(uint32_t aIndex, const Point3D &aPoint) +{ + if (mLight.SetAttribute(aIndex, aPoint)) { + Invalidate(); + return; + } + MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute point"); +} + +template +void +FilterNodeLightingSoftware::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 +void +FilterNodeLightingSoftware::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 +void +FilterNodeLightingSoftware::SetAttribute(uint32_t aIndex, const Color &aColor) +{ + MOZ_ASSERT(aIndex == ATT_LIGHTING_COLOR); + mColor = aColor; + Invalidate(); +} + +template +IntRect +FilterNodeLightingSoftware::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(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 +static Point3D +GenerateNormal(const uint8_t *data, int32_t stride, + 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, -dx, -dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, dx, -dy, 1, 0) + + -2 * ColorComponentAtPoint(index, stride, -dx, zero, 1, 0) + + 2 * ColorComponentAtPoint(index, stride, dx, zero, 1, 0) + + -1 * ColorComponentAtPoint(index, stride, -dx, dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, dx, dy, 1, 0); + + int16_t normalY = + -1 * ColorComponentAtPoint(index, stride, -dx, -dy, 1, 0) + + -2 * ColorComponentAtPoint(index, stride, zero, -dy, 1, 0) + + -1 * ColorComponentAtPoint(index, stride, dx, -dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, -dx, dy, 1, 0) + + 2 * ColorComponentAtPoint(index, stride, zero, dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, 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 +already_AddRefed +FilterNodeLightingSoftware::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 +void +FilterNodeLightingSoftware::RequestFromInputsForRect(const IntRect &aRect) +{ + IntRect srcRect = aRect; + srcRect.Inflate(ceil(mKernelUnitLength.width), + ceil(mKernelUnitLength.height)); + RequestInputRect(IN_LIGHTING_IN, srcRect); +} + +template template +already_AddRefed +FilterNodeLightingSoftware::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 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); + } + + DebugOnlyAutoColorSamplingAccessControl accessControl(input); + + RefPtr 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* 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, + x, y, mSurfaceScale, + aKernelUnitLengthX, aKernelUnitLengthY); + + IntPoint pointInFilterSpace(aRect.x + x, aRect.y + y); + Float Z = mSurfaceScale * sourceData[sourceIndex] / 255.0f; + Point3D pt(pointInFilterSpace.x, pointInFilterSpace.y, Z); + Point3D rayDir = mLight.GetVectorToLight(pt); + uint32_t color = mLight.GetColor(lightColor, rayDir); + + *(uint32_t*)(targetData + targetIndex) = mLighting.LightPixel(normal, rayDir, color); + } + + // Zero padding to keep valgrind happy. + PodZero(&targetData[y * targetStride + 4 * size.width], targetStride - 4 * size.width); + } + + return target.forget(); +} + +DiffuseLightingSoftware::DiffuseLightingSoftware() + : mDiffuseConstant(0) +{ +} + +bool +DiffuseLightingSoftware::SetAttribute(uint32_t aIndex, Float aValue) +{ + switch (aIndex) { + case ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT: + mDiffuseConstant = aValue; + break; + default: + return false; + } + return true; +} + +uint32_t +DiffuseLightingSoftware::LightPixel(const Point3D &aNormal, + const Point3D &aVectorToLight, + uint32_t aColor) +{ + Float dotNL = std::max(0.0f, aNormal.DotProduct(aVectorToLight)); + Float diffuseNL = mDiffuseConstant * dotNL; + + union { + uint32_t bgra; + uint8_t components[4]; + } color = { aColor }; + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = + umin(uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B]), 255U); + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = + umin(uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G]), 255U); + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = + umin(uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]), 255U); + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = 255; + return color.bgra; +} + +SpecularLightingSoftware::SpecularLightingSoftware() + : mSpecularConstant(0) + , mSpecularExponent(0) + , mSpecularConstantInt(0) +{ +} + +bool +SpecularLightingSoftware::SetAttribute(uint32_t aIndex, Float aValue) +{ + switch (aIndex) { + case ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT: + mSpecularConstant = std::min(std::max(aValue, 0.0f), 255.0f); + break; + case ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT: + mSpecularExponent = std::min(std::max(aValue, 1.0f), 128.0f); + break; + default: + return false; + } + return true; +} + +void +SpecularLightingSoftware::Prepare() +{ + mPowCache.CacheForExponent(mSpecularExponent); + mSpecularConstantInt = uint32_t(mSpecularConstant * (1 << 8)); +} + +uint32_t +SpecularLightingSoftware::LightPixel(const Point3D &aNormal, + const Point3D &aVectorToLight, + uint32_t aColor) +{ + Point3D vectorToEye(0, 0, 1); + Point3D halfwayVector = Normalized(aVectorToLight + vectorToEye); + Float dotNH = aNormal.DotProduct(halfwayVector); + uint16_t dotNHi = uint16_t(dotNH * (dotNH >= 0) * (1 << PowCache::sInputIntPrecisionBits)); + // The exponent for specular is in [1,128] range, so we don't need to check and + // optimize for the "default power table" scenario here. + MOZ_ASSERT(mPowCache.HasPowerTable()); + uint32_t specularNHi = uint32_t(mSpecularConstantInt) * mPowCache.Pow(dotNHi) >> 8; + + union { + uint32_t bgra; + uint8_t components[4]; + } color = { aColor }; + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = + umin( + (specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B]) >> PowCache::sOutputIntPrecisionBits, 255U); + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = + umin( + (specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G]) >> PowCache::sOutputIntPrecisionBits, 255U); + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = + umin( + (specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]) >> PowCache::sOutputIntPrecisionBits, 255U); + + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = + umax(color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B], + umax(color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G], + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R])); + return color.bgra; +} + +} // namespace gfx +} // namespace mozilla -- cgit v1.2.3