/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #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); } static 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.SetRectX(0, aFromRect.X()); sampleRect.SetRectX(fill.XMost(), 1); break; case 1: fill.SetRectX(aFromRect.X(), aFromRect.Width()); sampleRect.SetRectX(fill.X(), fill.Width()); break; case 2: fill.MoveToX(aFromRect.XMost()); fill.SetRightEdge(size.width); sampleRect.SetRectX(fill.X() - 1, 1); break; } if (fill.Width() <= 0) { continue; } bool xIsMiddle = (ix == 1); for (int32_t iy = 0; iy < 3; iy++) { switch (iy) { case 0: fill.SetRectY(0, aFromRect.Y()); sampleRect.SetRectY(fill.YMost(), 1); break; case 1: fill.SetRectY(aFromRect.Y(), aFromRect.Height()); sampleRect.SetRectY(fill.Y(), fill.Height()); break; case 2: fill.MoveToY(aFromRect.YMost()); fill.SetBottomEdge(size.height); sampleRect.SetRectY(fill.Y() - 1, 1); break; } if (fill.Height() <= 0) { continue; } bool yIsMiddle = (iy == 1); if (!xIsMiddle && !yIsMiddle) { // Corner FillRectWithPixel(aSurface, fill, sampleRect.TopLeft()); } if (xIsMiddle && !yIsMiddle) { // Top middle or bottom middle FillRectWithVerticallyRepeatingHorizontalStrip(aSurface, fill, sampleRect); } if (!xIsMiddle && yIsMiddle) { // Left middle or right middle FillRectWithHorizontallyRepeatingVerticalStrip(aSurface, fill, sampleRect); } } } } static IntPoint TileIndex(const IntRect& aFirstTileRect, const IntPoint& aPoint) { return IntPoint(int32_t(floor(double(aPoint.x - aFirstTileRect.X()) / aFirstTileRect.Width())), int32_t(floor(double(aPoint.y - aFirstTileRect.Y()) / aFirstTileRect.Height()))); } static void TileSurface(DataSourceSurface* aSource, DataSourceSurface* aTarget, const IntPoint& aOffset) { IntRect sourceRect(aOffset, aSource->GetSize()); IntRect targetRect(IntPoint(0, 0), aTarget->GetSize()); IntPoint startIndex = TileIndex(sourceRect, targetRect.TopLeft()); IntPoint endIndex = TileIndex(sourceRect, targetRect.BottomRight()); for (int32_t ix = startIndex.x; ix <= endIndex.x; ix++) { for (int32_t iy = startIndex.y; iy <= endIndex.y; iy++) { IntPoint destPoint(sourceRect.X() + ix * sourceRect.Width(), sourceRect.Y() + iy * sourceRect.Height()); IntRect destRect(destPoint, sourceRect.Size()); destRect = destRect.Intersect(targetRect); IntRect srcRect = destRect - destPoint; CopyRect(aSource, aTarget, srcRect, destRect.TopLeft()); } } } static already_AddRefed GetDataSurfaceInRect( SourceSurface* aSurface, const IntRect& aSurfaceRect, const IntRect& aDestRect, ConvolveMatrixEdgeMode aEdgeMode) { MOZ_ASSERT(aSurface ? aSurfaceRect.Size() == aSurface->GetSize() : aSurfaceRect.IsEmpty()); if (aSurfaceRect.Overflows() || aDestRect.Overflows()) { // We can't rely on the intersection calculations below to make sense when // XMost() or YMost() overflow. Bail out. return nullptr; } IntRect sourceRect = aSurfaceRect; if (sourceRect.IsEqualEdges(aDestRect)) { return aSurface ? aSurface->GetDataSurface() : nullptr; } IntRect intersect = sourceRect.Intersect(aDestRect); // create rects that are in surface local space. IntRect intersectInSourceSpace = intersect - sourceRect.TopLeft(); IntRect intersectInDestSpace = intersect - aDestRect.TopLeft(); SurfaceFormat format = aSurface ? aSurface->GetFormat() : SurfaceFormat(SurfaceFormat::B8G8R8A8); RefPtr 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::OPACITY: filter = new FilterNodeOpacitySoftware(); 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(DeviceColor::MaskOpaqueBlack()), 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; } IntRect cachedRect; IntRect requestedRect; RefPtr cachedOutput; // Retrieve a cached surface if we have one and it can // satisfy this request, or else request a rect we will compute and cache if (!mCachedRect.Contains(aRect)) { RequestRect(aRect); requestedRect = mRequestedRect; } else { MOZ_ASSERT(mCachedOutput, "cached rect but no cached output?"); cachedRect = mCachedRect; cachedOutput = mCachedOutput; } if (!cachedOutput) { // Compute the output cachedOutput = Render(requestedRect); // Update the cache for future requests mCachedOutput = cachedOutput; if (!mCachedOutput) { mCachedRect = IntRect(); mRequestedRect = IntRect(); return nullptr; } mCachedRect = requestedRect; mRequestedRect = IntRect(); cachedRect = mCachedRect; } return GetDataSurfaceInRect(cachedOutput, cachedRect, aRect, EDGE_MODE_NONE); } void FilterNodeSoftware::RequestRect(const IntRect& aRect) { if (mRequestedRect.Contains(aRect)) { // Bail out now. Otherwise pathological filters can spend time exponential // in the number of primitives, e.g. if each primitive takes the // previous primitive as its two inputs. return; } mRequestedRect = mRequestedRect.Union(aRect); RequestFromInputsForRect(aRect); } IntRect FilterNodeSoftware::MapInputRectToSource(uint32_t aInputEnumIndex, const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { int32_t inputIndex = InputIndex(aInputEnumIndex); if (inputIndex < 0) { gfxDevCrash(LogReason::FilterInputError) << "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs(); return aMax; } if ((uint32_t)inputIndex < NumberOfSetInputs()) { RefPtr filter = mInputFilters[inputIndex]; // If we have any input filters call into them to do the mapping, // otherwise we can assume an input surface will be used // and just return aRect. if (filter) { return filter->MapRectToSource(aRect, aMax, aSourceNode); } } // We have an input surface instead of a filter // so check if we're the target node. if (this == aSourceNode) { return aRect; } return IntRect(); } void FilterNodeSoftware::RequestInputRect(uint32_t aInputEnumIndex, const IntRect& aRect) { if (aRect.Overflows()) { return; } int32_t inputIndex = InputIndex(aInputEnumIndex); if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) { gfxDevCrash(LogReason::FilterInputError) << "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs(); return; } if (mInputSurfaces[inputIndex]) { return; } RefPtr 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 = surface->GetRect(); } 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); if (surface) { surfaceRect = srcRect; } else { // Padding the surface with transparency failed, probably due to size // restrictions. Since |surface| is now null, set the surfaceRect to // empty so that we're consistent. surfaceRect.SetEmpty(); } } RefPtr 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(mInputSurfaces[inputIndex]->GetRect()); } 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() {} FilterNodeSoftware::~FilterNodeSoftware() { MOZ_ASSERT( mInvalidationListeners.empty(), "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; } MOZ_ASSERT_UNREACHABLE("Unexpected BlendMode"); 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::SKIA, targetMap.GetData(), target->GetSize(), targetMap.GetStride(), target->GetFormat()); if (!dt) { gfxWarning() << "FilterNodeBlendSoftware::Render failed in CreateDrawTargetForData"; return nullptr; } Rect r(0, 0, size.width, size.height); dt->DrawSurface(input2, r, r, DrawSurfaceOptions(), DrawOptions(1.0f, ToBlendOp(mBlendMode))); dt->Flush(); return target.forget(); } void FilterNodeBlendSoftware::RequestFromInputsForRect(const IntRect& aRect) { RequestInputRect(IN_BLEND_IN, aRect); RequestInputRect(IN_BLEND_IN2, aRect); } IntRect FilterNodeBlendSoftware::MapRectToSource(const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { IntRect result = MapInputRectToSource(IN_BLEND_IN, aRect, aMax, aSourceNode); result.OrWith(MapInputRectToSource(IN_BLEND_IN2, aRect, aMax, aSourceNode)); return result; } IntRect FilterNodeBlendSoftware::GetOutputRectInRect(const IntRect& aRect) { return GetInputRectInRect(IN_BLEND_IN, aRect) .Union(GetInputRectInRect(IN_BLEND_IN2, aRect)) .Intersect(aRect); } FilterNodeTransformSoftware::FilterNodeTransformSoftware() : mSamplingFilter(SamplingFilter::GOOD) {} int32_t FilterNodeTransformSoftware::InputIndex(uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_TRANSFORM_IN: return 0; default: return -1; } } void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex, uint32_t aFilter) { MOZ_ASSERT(aIndex == ATT_TRANSFORM_FILTER); mSamplingFilter = static_cast(aFilter); Invalidate(); } void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex, const Matrix& aMatrix) { MOZ_ASSERT(aIndex == ATT_TRANSFORM_MATRIX); mMatrix = aMatrix; Invalidate(); } IntRect FilterNodeTransformSoftware::SourceRectForOutputRect( const IntRect& aRect) { if (aRect.IsEmpty()) { return IntRect(); } Matrix inverted(mMatrix); if (!inverted.Invert()) { return IntRect(); } Rect neededRect = inverted.TransformBounds(Rect(aRect)); neededRect.RoundOut(); IntRect neededIntRect; if (!neededRect.ToIntRect(&neededIntRect)) { return IntRect(); } return GetInputRectInRect(IN_TRANSFORM_IN, neededIntRect); } IntRect FilterNodeTransformSoftware::MapRectToSource(const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { if (aRect.IsEmpty()) { return IntRect(); } Matrix inverted(mMatrix); if (!inverted.Invert()) { return aMax; } Rect neededRect = inverted.TransformBounds(Rect(aRect)); neededRect.RoundOut(); IntRect neededIntRect; if (!neededRect.ToIntRect(&neededIntRect)) { return aMax; } return MapInputRectToSource(IN_TRANSFORM_IN, neededIntRect, aMax, aSourceNode); } already_AddRefed 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::SKIA, mapping.mData, surf->GetSize(), mapping.mStride, surf->GetFormat()); if (!dt) { gfxWarning() << "FilterNodeTransformSoftware::Render failed in " "CreateDrawTargetForData"; return nullptr; } Rect r(0, 0, srcRect.Width(), srcRect.Height()); dt->SetTransform(transform); dt->DrawSurface(input, r, r, DrawSurfaceOptions(mSamplingFilter)); dt->Flush(); surf->Unmap(); return surf.forget(); } void FilterNodeTransformSoftware::RequestFromInputsForRect( const IntRect& aRect) { RequestInputRect(IN_TRANSFORM_IN, SourceRectForOutputRect(aRect)); } IntRect FilterNodeTransformSoftware::GetOutputRectInRect(const IntRect& aRect) { IntRect srcRect = SourceRectForOutputRect(aRect); if (srcRect.IsEmpty()) { return IntRect(); } Rect outRect = mMatrix.TransformBounds(Rect(srcRect)); outRect.RoundOut(); IntRect outIntRect; if (!outRect.ToIntRect(&outIntRect)) { return IntRect(); } return outIntRect.Intersect(aRect); } FilterNodeMorphologySoftware::FilterNodeMorphologySoftware() : mOperator(MORPHOLOGY_OPERATOR_ERODE) {} int32_t FilterNodeMorphologySoftware::InputIndex(uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_MORPHOLOGY_IN: return 0; default: return -1; } } void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex, const IntSize& aRadii) { MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_RADII); mRadii.width = std::min(std::max(aRadii.width, 0), 100000); mRadii.height = std::min(std::max(aRadii.height, 0), 100000); Invalidate(); } void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex, uint32_t aOperator) { MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_OPERATOR); mOperator = static_cast(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(); } static already_AddRefed Opacity(DataSourceSurface* aSurface, Float aValue) { if (aValue == 1.0f) { RefPtr surface(aSurface); return surface.forget(); } IntSize size = aSurface->GetSize(); RefPtr target = Factory::CreateDataSourceSurface(size, aSurface->GetFormat()); if (MOZ2D_WARN_IF(!target)) { return nullptr; } DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ); DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) { return nullptr; } uint8_t* inputData = inputMap.GetData(); int32_t inputStride = inputMap.GetStride(); uint8_t* targetData = targetMap.GetData(); int32_t targetStride = targetMap.GetStride(); if (aSurface->GetFormat() == SurfaceFormat::A8) { FilterProcessing::DoOpacityCalculationA8(size, targetData, targetStride, inputData, inputStride, aValue); } else { MOZ_ASSERT(aSurface->GetFormat() == SurfaceFormat::B8G8R8A8); FilterProcessing::DoOpacityCalculation(size, targetData, targetStride, inputData, inputStride, aValue); } return target.forget(); } already_AddRefed 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::MapRectToSource( const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { return MapInputRectToSource(IN_COLOR_MATRIX_IN, aRect, aMax, aSourceNode); } IntRect FilterNodeColorMatrixSoftware::GetOutputRectInRect( const IntRect& aRect) { if (mMatrix._54 > 0.0f) { return aRect; } return GetInputRectInRect(IN_COLOR_MATRIX_IN, aRect); } void FilterNodeFloodSoftware::SetAttribute(uint32_t aIndex, const DeviceColor& aColor) { MOZ_ASSERT(aIndex == ATT_FLOOD_COLOR); mColor = aColor; Invalidate(); } static uint32_t ColorToBGRA(const DeviceColor& aColor) { union { uint32_t color; uint8_t components[4]; }; components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = NS_lround(aColor.r * aColor.a * 255.0f); components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = NS_lround(aColor.g * aColor.a * 255.0f); components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = NS_lround(aColor.b * aColor.a * 255.0f); components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = NS_lround(aColor.a * 255.0f); return color; } static SurfaceFormat FormatForColor(DeviceColor aColor) { if (aColor.r == 0 && aColor.g == 0 && aColor.b == 0) { return SurfaceFormat::A8; } return SurfaceFormat::B8G8R8A8; } already_AddRefed 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::MapRectToSource(const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { return IntRect(); } IntRect FilterNodeFloodSoftware::GetOutputRectInRect(const IntRect& aRect) { if (mColor.a == 0.0f) { return IntRect(); } return aRect; } int32_t FilterNodeTileSoftware::InputIndex(uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_TILE_IN: return 0; default: return -1; } } void FilterNodeTileSoftware::SetAttribute(uint32_t aIndex, const IntRect& aSourceRect) { MOZ_ASSERT(aIndex == ATT_TILE_SOURCE_RECT); mSourceRect.SetRect(int32_t(aSourceRect.X()), int32_t(aSourceRect.Y()), int32_t(aSourceRect.Width()), int32_t(aSourceRect.Height())); Invalidate(); } namespace { struct CompareIntRects { bool operator()(const IntRect& a, const IntRect& b) const { if (a.X() != b.X()) { return a.X() < b.X(); } if (a.Y() != b.Y()) { return a.Y() < b.Y(); } if (a.Width() != b.Width()) { return a.Width() < b.Width(); } return a.Height() < b.Height(); } }; } // namespace already_AddRefed 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) { for (int32_t i = 0; i < 256; ++i) { aTables[aComponent][i] = i; } } 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); } } static bool IsAllZero(const uint8_t aLookupTable[256]) { for (int32_t i = 0; i < 256; i++) { if (aLookupTable[i] != 0) { return false; } } return true; } already_AddRefed 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::MapRectToSource( const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { return MapInputRectToSource(IN_TRANSFER_IN, aRect, aMax, aSourceNode); } IntRect FilterNodeComponentTransferSoftware::GetOutputRectInRect( const IntRect& aRect) { if (mDisableA) { return GetInputRectInRect(IN_TRANSFER_IN, aRect); } return aRect; } int32_t FilterNodeComponentTransferSoftware::InputIndex( uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_TRANSFER_IN: return 0; default: return -1; } } void FilterNodeTableTransferSoftware::SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) { std::vector 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), mOffsetR(0.0), mOffsetG(0.0), mOffsetB(0.0), mOffsetA(0.0) {} void FilterNodeGammaTransferSoftware::SetAttribute(uint32_t aIndex, Float aValue) { switch (aIndex) { case ATT_GAMMA_TRANSFER_AMPLITUDE_R: mAmplitudeR = aValue; break; case ATT_GAMMA_TRANSFER_EXPONENT_R: mExponentR = aValue; break; case ATT_GAMMA_TRANSFER_OFFSET_R: mOffsetR = aValue; break; case ATT_GAMMA_TRANSFER_AMPLITUDE_G: mAmplitudeG = aValue; break; case ATT_GAMMA_TRANSFER_EXPONENT_G: mExponentG = aValue; break; case ATT_GAMMA_TRANSFER_OFFSET_G: mOffsetG = aValue; break; case ATT_GAMMA_TRANSFER_AMPLITUDE_B: mAmplitudeB = aValue; break; case ATT_GAMMA_TRANSFER_EXPONENT_B: mExponentB = aValue; break; case ATT_GAMMA_TRANSFER_OFFSET_B: mOffsetB = aValue; break; case ATT_GAMMA_TRANSFER_AMPLITUDE_A: mAmplitudeA = aValue; break; case ATT_GAMMA_TRANSFER_EXPONENT_A: mExponentA = aValue; break; case ATT_GAMMA_TRANSFER_OFFSET_A: mOffsetA = aValue; break; default: MOZ_CRASH("GFX: FilterNodeGammaTransferSoftware::SetAttribute"); } Invalidate(); } void FilterNodeGammaTransferSoftware::FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) { switch (aComponent) { case B8G8R8A8_COMPONENT_BYTEOFFSET_R: FillLookupTableImpl(mAmplitudeR, mExponentR, mOffsetR, aTable); break; case B8G8R8A8_COMPONENT_BYTEOFFSET_G: FillLookupTableImpl(mAmplitudeG, mExponentG, mOffsetG, aTable); break; case B8G8R8A8_COMPONENT_BYTEOFFSET_B: FillLookupTableImpl(mAmplitudeB, mExponentB, mOffsetB, aTable); break; case B8G8R8A8_COMPONENT_BYTEOFFSET_A: FillLookupTableImpl(mAmplitudeA, mExponentA, mOffsetA, aTable); break; default: MOZ_ASSERT(false, "unknown component"); break; } } void FilterNodeGammaTransferSoftware::FillLookupTableImpl(Float aAmplitude, Float aExponent, Float aOffset, uint8_t aTable[256]) { for (size_t i = 0; i < 256; i++) { int32_t val = NS_lround(255 * (aAmplitude * pow(i / 255.0f, aExponent) + aOffset)); val = std::min(255, val); val = std::max(0, val); aTable[i] = val; } } FilterNodeConvolveMatrixSoftware::FilterNodeConvolveMatrixSoftware() : mDivisor(0), mBias(0), mEdgeMode(EDGE_MODE_DUPLICATE), mPreserveAlpha(false) {} int32_t FilterNodeConvolveMatrixSoftware::InputIndex(uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_CONVOLVE_MATRIX_IN: return 0; default: return -1; } } void FilterNodeConvolveMatrixSoftware::SetAttribute( uint32_t aIndex, const IntSize& aKernelSize) { MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_SIZE); mKernelSize = aKernelSize; Invalidate(); } void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, const Float* aMatrix, uint32_t aSize) { MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_MATRIX); mKernelMatrix = std::vector(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 inline void DebugOnlyCheckColorSamplingAccess( const uint8_t* aSampleAddress, const uint8_t* aBoundsBegin, const uint8_t* aBoundsEnd) { MOZ_ASSERT(aSampleAddress >= aBoundsBegin, "accessing before start"); MOZ_ASSERT(aSampleAddress < aBoundsEnd, "accessing after end"); } #else # define DebugOnlyCheckColorSamplingAccess(address, boundsBegin, boundsEnd) #endif static inline uint8_t ColorComponentAtPoint(const uint8_t* aData, ptrdiff_t aStride, const uint8_t* aBoundsBegin, const uint8_t* aBoundsEnd, int32_t x, int32_t y, ptrdiff_t bpp, ptrdiff_t c) { DebugOnlyCheckColorSamplingAccess(&aData[y * aStride + bpp * x + c], aBoundsBegin, aBoundsEnd); return aData[y * aStride + bpp * x + c]; } static inline int32_t ColorAtPoint(const uint8_t* aData, ptrdiff_t aStride, const uint8_t* aBoundsBegin, const uint8_t* aBoundsEnd, int32_t x, int32_t y) { DebugOnlyCheckColorSamplingAccess(aData + y * aStride + 4 * x, aBoundsBegin, aBoundsEnd); return *(uint32_t*)(aData + y * aStride + 4 * x); } // Accepts fractional x & y and does bilinear interpolation. // Only call this if the pixel (floor(x)+1, floor(y)+1) is accessible. static inline uint8_t ColorComponentAtPoint( const uint8_t* aData, ptrdiff_t aStride, const uint8_t* aBoundsBegin, const uint8_t* aBoundsEnd, Float x, Float y, ptrdiff_t bpp, ptrdiff_t c) { const uint32_t f = 256; const int32_t lx = floor(x); const int32_t ly = floor(y); const int32_t tux = uint32_t((x - lx) * f); const int32_t tlx = f - tux; const int32_t tuy = uint32_t((y - ly) * f); const int32_t tly = f - tuy; const uint8_t& cll = ColorComponentAtPoint(aData, aStride, aBoundsBegin, aBoundsEnd, lx, ly, bpp, c); const uint8_t& cul = ColorComponentAtPoint(aData, aStride, aBoundsBegin, aBoundsEnd, lx + 1, ly, bpp, c); const uint8_t& clu = ColorComponentAtPoint(aData, aStride, aBoundsBegin, aBoundsEnd, lx, ly + 1, bpp, c); const uint8_t& cuu = ColorComponentAtPoint( aData, aStride, aBoundsBegin, aBoundsEnd, lx + 1, ly + 1, bpp, c); return ((cll * tlx + cul * tux) * tly + (clu * tlx + cuu * tux) * tuy + f * f / 2) / (f * f); } static int32_t ClampToNonZero(int32_t a) { return a * (a >= 0); } template static void ConvolvePixel(const uint8_t* aSourceData, uint8_t* aTargetData, int32_t aWidth, int32_t aHeight, int32_t aSourceStride, int32_t aTargetStride, const uint8_t* aSourceBegin, const uint8_t* aSourceEnd, int32_t aX, int32_t aY, const int32_t* aKernel, int32_t aBias, int32_t shiftL, int32_t shiftR, bool aPreserveAlpha, int32_t aOrderX, int32_t aOrderY, int32_t aTargetX, int32_t aTargetY, CoordType aKernelUnitLengthX, CoordType aKernelUnitLengthY) { int32_t sum[4] = {0, 0, 0, 0}; int32_t offsets[4] = { B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G, B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A}; int32_t channels = aPreserveAlpha ? 3 : 4; int32_t roundingAddition = shiftL == 0 ? 0 : 1 << (shiftL - 1); for (int32_t y = 0; y < aOrderY; y++) { CoordType sampleY = aY + (y - aTargetY) * aKernelUnitLengthY; for (int32_t x = 0; x < aOrderX; x++) { CoordType sampleX = aX + (x - aTargetX) * aKernelUnitLengthX; for (int32_t i = 0; i < channels; i++) { sum[i] += aKernel[aOrderX * y + x] * ColorComponentAtPoint(aSourceData, aSourceStride, aSourceBegin, aSourceEnd, sampleX, sampleY, 4, offsets[i]); } } } for (int32_t i = 0; i < channels; i++) { int32_t clamped = umin(ClampToNonZero(sum[i] + aBias), 255 << shiftL >> shiftR); aTargetData[aY * aTargetStride + 4 * aX + offsets[i]] = (clamped + roundingAddition) << shiftR >> shiftL; } if (aPreserveAlpha) { aTargetData[aY * aTargetStride + 4 * aX + B8G8R8A8_COMPONENT_BYTEOFFSET_A] = aSourceData[aY * aSourceStride + 4 * aX + B8G8R8A8_COMPONENT_BYTEOFFSET_A]; } } already_AddRefed 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; } 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* sourceBegin = sourceMap.GetData(); uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height; uint8_t* targetData = targetMap.GetData(); int32_t targetStride = targetMap.GetStride(); // Why exactly are we reversing the kernel? std::vector kernel = ReversedVector(mKernelMatrix); kernel = ScaledVector(kernel, mDivisor); Float maxResultAbs = std::max(MaxVectorSum(kernel) + mBias, MaxVectorSum(ScaledVector(kernel, -1)) - mBias); maxResultAbs = std::max(maxResultAbs, 1.0f); double idealFactor = INT32_MAX / 2.0 / maxResultAbs / 255.0 * 0.999; MOZ_ASSERT(255.0 * maxResultAbs * idealFactor <= INT32_MAX / 2.0, "badly chosen float-to-int scale"); int32_t shiftL, shiftR; TranslateDoubleToShifts(idealFactor, shiftL, shiftR); double factorFromShifts = Float(1 << shiftL) / Float(1 << shiftR); MOZ_ASSERT(255.0 * maxResultAbs * factorFromShifts <= INT32_MAX / 2.0, "badly chosen float-to-int scale"); int32_t* intKernel = new int32_t[kernel.size()]; for (size_t i = 0; i < kernel.size(); i++) { intKernel[i] = NS_lround(kernel[i] * factorFromShifts); } int32_t bias = NS_lround(mBias * 255 * factorFromShifts); for (int32_t y = 0; y < aRect.Height(); y++) { for (int32_t x = 0; x < aRect.Width(); x++) { ConvolvePixel(sourceData, targetData, aRect.Width(), aRect.Height(), sourceStride, targetStride, sourceBegin, sourceEnd, x, y, intKernel, bias, shiftL, shiftR, mPreserveAlpha, mKernelSize.width, mKernelSize.height, mTarget.x, mTarget.y, aKernelUnitLengthX, aKernelUnitLengthY); } } delete[] intKernel; return target.forget(); } void FilterNodeConvolveMatrixSoftware::RequestFromInputsForRect( const IntRect& aRect) { RequestInputRect(IN_CONVOLVE_MATRIX_IN, InflatedSourceRect(aRect)); } IntRect FilterNodeConvolveMatrixSoftware::MapRectToSource( const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { return MapInputRectToSource(IN_CONVOLVE_MATRIX_IN, InflatedSourceRect(aRect), aMax, aSourceNode); } IntRect FilterNodeConvolveMatrixSoftware::InflatedSourceRect( const IntRect& aDestRect) { if (aDestRect.IsEmpty()) { return IntRect(); } IntMargin margin; margin.left = ceil(mTarget.x * mKernelUnitLength.width); margin.top = ceil(mTarget.y * mKernelUnitLength.height); margin.right = ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width); margin.bottom = ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height); IntRect srcRect = aDestRect; srcRect.Inflate(margin); return srcRect; } IntRect FilterNodeConvolveMatrixSoftware::InflatedDestRect( const IntRect& aSourceRect) { if (aSourceRect.IsEmpty()) { return IntRect(); } IntMargin margin; margin.left = ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width); margin.top = ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height); margin.right = ceil(mTarget.x * mKernelUnitLength.width); margin.bottom = ceil(mTarget.y * mKernelUnitLength.height); IntRect destRect = aSourceRect; destRect.Inflate(margin); return destRect; } IntRect FilterNodeConvolveMatrixSoftware::GetOutputRectInRect( const IntRect& aRect) { IntRect srcRequest = InflatedSourceRect(aRect); IntRect srcOutput = GetInputRectInRect(IN_COLOR_MATRIX_IN, srcRequest); return InflatedDestRect(srcOutput).Intersect(aRect); } FilterNodeDisplacementMapSoftware::FilterNodeDisplacementMapSoftware() : mScale(0.0f), mChannelX(COLOR_CHANNEL_R), mChannelY(COLOR_CHANNEL_G) {} int32_t FilterNodeDisplacementMapSoftware::InputIndex( uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_DISPLACEMENT_MAP_IN: return 0; case IN_DISPLACEMENT_MAP_IN2: return 1; default: return -1; } } void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex, Float aScale) { MOZ_ASSERT(aIndex == ATT_DISPLACEMENT_MAP_SCALE); mScale = aScale; Invalidate(); } void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex, uint32_t aValue) { switch (aIndex) { case ATT_DISPLACEMENT_MAP_X_CHANNEL: mChannelX = static_cast(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* sourceBegin = inputMap.GetData(); uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height; uint8_t* mapData = mapMap.GetData(); int32_t mapStride = mapMap.GetStride(); uint8_t* targetData = targetMap.GetData(); int32_t targetStride = targetMap.GetStride(); static const ptrdiff_t channelMap[4] = { B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G, B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A}; uint16_t xChannel = channelMap[mChannelX]; uint16_t yChannel = channelMap[mChannelY]; float scaleOver255 = mScale / 255.0f; float scaleAdjustment = -0.5f * mScale; for (int32_t y = 0; y < aRect.Height(); y++) { for (int32_t x = 0; x < aRect.Width(); x++) { uint32_t mapIndex = y * mapStride + 4 * x; uint32_t targIndex = y * targetStride + 4 * x; int32_t sourceX = x + scaleOver255 * mapData[mapIndex + xChannel] + scaleAdjustment; int32_t sourceY = y + scaleOver255 * mapData[mapIndex + yChannel] + scaleAdjustment; *(uint32_t*)(targetData + targIndex) = ColorAtPoint( sourceData, sourceStride, sourceBegin, sourceEnd, sourceX, sourceY); } // Keep valgrind happy. PodZero(&targetData[y * targetStride + 4 * aRect.Width()], targetStride - 4 * aRect.Width()); } return target.forget(); } void FilterNodeDisplacementMapSoftware::RequestFromInputsForRect( const IntRect& aRect) { RequestInputRect(IN_DISPLACEMENT_MAP_IN, InflatedSourceOrDestRect(aRect)); RequestInputRect(IN_DISPLACEMENT_MAP_IN2, aRect); } IntRect FilterNodeDisplacementMapSoftware::MapRectToSource( const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { IntRect result = MapInputRectToSource(IN_DISPLACEMENT_MAP_IN, InflatedSourceOrDestRect(aRect), aMax, aSourceNode); result.OrWith( MapInputRectToSource(IN_DISPLACEMENT_MAP_IN2, aRect, aMax, aSourceNode)); return result; } IntRect FilterNodeDisplacementMapSoftware::InflatedSourceOrDestRect( const IntRect& aDestOrSourceRect) { IntRect sourceOrDestRect = aDestOrSourceRect; sourceOrDestRect.Inflate(ceil(fabs(mScale) / 2)); return sourceOrDestRect; } IntRect FilterNodeDisplacementMapSoftware::GetOutputRectInRect( const IntRect& aRect) { IntRect srcRequest = InflatedSourceOrDestRect(aRect); IntRect srcOutput = GetInputRectInRect(IN_DISPLACEMENT_MAP_IN, srcRequest); return InflatedSourceOrDestRect(srcOutput).Intersect(aRect); } FilterNodeTurbulenceSoftware::FilterNodeTurbulenceSoftware() : mNumOctaves(0), mSeed(0), mStitchable(false), mType(TURBULENCE_TYPE_TURBULENCE) {} int32_t FilterNodeTurbulenceSoftware::InputIndex(uint32_t aInputEnumIndex) { return -1; } void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, const Size& aBaseFrequency) { switch (aIndex) { case ATT_TURBULENCE_BASE_FREQUENCY: mBaseFrequency = aBaseFrequency; break; default: MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute"); break; } Invalidate(); } void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, const IntRect& aRect) { switch (aIndex) { case ATT_TURBULENCE_RECT: mRenderRect = aRect; break; default: MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute"); break; } Invalidate(); } void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, bool aStitchable) { MOZ_ASSERT(aIndex == ATT_TURBULENCE_STITCHABLE); mStitchable = aStitchable; Invalidate(); } void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, uint32_t aValue) { switch (aIndex) { case ATT_TURBULENCE_NUM_OCTAVES: mNumOctaves = aValue; break; case ATT_TURBULENCE_SEED: mSeed = aValue; break; case ATT_TURBULENCE_TYPE: mType = static_cast(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); } IntRect FilterNodeTurbulenceSoftware::MapRectToSource(const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { return IntRect(); } FilterNodeArithmeticCombineSoftware::FilterNodeArithmeticCombineSoftware() : mK1(0), mK2(0), mK3(0), mK4(0) {} int32_t FilterNodeArithmeticCombineSoftware::InputIndex( uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_ARITHMETIC_COMBINE_IN: return 0; case IN_ARITHMETIC_COMBINE_IN2: return 1; default: return -1; } } void FilterNodeArithmeticCombineSoftware::SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) { MOZ_ASSERT(aIndex == ATT_ARITHMETIC_COMBINE_COEFFICIENTS); MOZ_RELEASE_ASSERT(aSize == 4); mK1 = aFloat[0]; mK2 = aFloat[1]; mK3 = aFloat[2]; mK4 = aFloat[3]; Invalidate(); } already_AddRefed 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::MapRectToSource( const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { IntRect result = MapInputRectToSource(IN_ARITHMETIC_COMBINE_IN, aRect, aMax, aSourceNode); result.OrWith(MapInputRectToSource(IN_ARITHMETIC_COMBINE_IN2, aRect, aMax, aSourceNode)); return result; } IntRect FilterNodeArithmeticCombineSoftware::GetOutputRectInRect( const IntRect& aRect) { if (mK4 > 0.0f) { return aRect; } IntRect rectFrom1 = GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN, aRect).Intersect(aRect); IntRect rectFrom2 = GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN2, aRect).Intersect(aRect); IntRect result; if (mK1 > 0.0f) { result = rectFrom1.Intersect(rectFrom2); } if (mK2 > 0.0f) { result = result.Union(rectFrom1); } if (mK3 > 0.0f) { result = result.Union(rectFrom2); } return result; } FilterNodeCompositeSoftware::FilterNodeCompositeSoftware() : mOperator(COMPOSITE_OPERATOR_OVER) {} int32_t FilterNodeCompositeSoftware::InputIndex(uint32_t aInputEnumIndex) { return aInputEnumIndex - IN_COMPOSITE_IN_START; } void FilterNodeCompositeSoftware::SetAttribute(uint32_t aIndex, uint32_t aCompositeOperator) { MOZ_ASSERT(aIndex == ATT_COMPOSITE_OPERATOR); mOperator = static_cast(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: case COMPOSITE_OPERATOR_LIGHTER: // dest is unchanged. break; case COMPOSITE_OPERATOR_OUT: // dest is now transparent, but it can become non-transparent again // when compositing additional inputs. ClearDataSourceSurface(dest); break; case COMPOSITE_OPERATOR_IN: // Transparency always wins. We're completely transparent now and // no additional input can get rid of that transparency. return nullptr; } } } return dest.forget(); } void FilterNodeCompositeSoftware::RequestFromInputsForRect( const IntRect& aRect) { for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) { RequestInputRect(IN_COMPOSITE_IN_START + inputIndex, aRect); } } IntRect FilterNodeCompositeSoftware::MapRectToSource(const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { IntRect result; for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) { result.OrWith(MapInputRectToSource(IN_COMPOSITE_IN_START + inputIndex, aRect, aMax, aSourceNode)); } return result; } IntRect FilterNodeCompositeSoftware::GetOutputRectInRect(const IntRect& aRect) { IntRect rect; for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) { IntRect inputRect = GetInputRectInRect(IN_COMPOSITE_IN_START + inputIndex, aRect); if (mOperator == COMPOSITE_OPERATOR_IN && inputIndex > 0) { rect = rect.Intersect(inputRect); } else { rect = rect.Union(inputRect); } } return rect; } int32_t FilterNodeBlurXYSoftware::InputIndex(uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_GAUSSIAN_BLUR_IN: return 0; default: return -1; } } already_AddRefed 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::MapRectToSource(const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { return MapInputRectToSource( IN_GAUSSIAN_BLUR_IN, InflatedSourceOrDestRect(aRect), aMax, aSourceNode); } IntRect FilterNodeBlurXYSoftware::InflatedSourceOrDestRect( const IntRect& aDestRect) { Size sigmaXY = StdDeviationXY(); IntSize d = AlphaBoxBlur::CalculateBlurRadius(Point(sigmaXY.width, sigmaXY.height)); IntRect srcRect = aDestRect; srcRect.Inflate(d); return srcRect; } IntRect FilterNodeBlurXYSoftware::GetOutputRectInRect(const IntRect& aRect) { IntRect srcRequest = InflatedSourceOrDestRect(aRect); IntRect srcOutput = GetInputRectInRect(IN_GAUSSIAN_BLUR_IN, srcRequest); return InflatedSourceOrDestRect(srcOutput).Intersect(aRect); } FilterNodeGaussianBlurSoftware::FilterNodeGaussianBlurSoftware() : mStdDeviation(0) {} static float ClampStdDeviation(float aStdDeviation) { // Cap software blur radius for performance reasons. return std::min(std::max(0.0f, aStdDeviation), 100.0f); } void FilterNodeGaussianBlurSoftware::SetAttribute(uint32_t aIndex, float aStdDeviation) { switch (aIndex) { case ATT_GAUSSIAN_BLUR_STD_DEVIATION: mStdDeviation = ClampStdDeviation(aStdDeviation); break; default: MOZ_CRASH("GFX: FilterNodeGaussianBlurSoftware::SetAttribute"); } Invalidate(); } Size FilterNodeGaussianBlurSoftware::StdDeviationXY() { return Size(mStdDeviation, mStdDeviation); } FilterNodeDirectionalBlurSoftware::FilterNodeDirectionalBlurSoftware() : mStdDeviation(0.0), mBlurDirection(BLUR_DIRECTION_X) {} void FilterNodeDirectionalBlurSoftware::SetAttribute(uint32_t aIndex, Float aStdDeviation) { switch (aIndex) { case ATT_DIRECTIONAL_BLUR_STD_DEVIATION: mStdDeviation = ClampStdDeviation(aStdDeviation); break; default: MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute"); } Invalidate(); } void FilterNodeDirectionalBlurSoftware::SetAttribute(uint32_t aIndex, uint32_t aBlurDirection) { switch (aIndex) { case ATT_DIRECTIONAL_BLUR_DIRECTION: mBlurDirection = (BlurDirection)aBlurDirection; break; default: MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute"); } Invalidate(); } Size FilterNodeDirectionalBlurSoftware::StdDeviationXY() { float sigmaX = mBlurDirection == BLUR_DIRECTION_X ? mStdDeviation : 0; float sigmaY = mBlurDirection == BLUR_DIRECTION_Y ? mStdDeviation : 0; return Size(sigmaX, sigmaY); } int32_t FilterNodeCropSoftware::InputIndex(uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_CROP_IN: return 0; default: return -1; } } void FilterNodeCropSoftware::SetAttribute(uint32_t aIndex, const Rect& aSourceRect) { MOZ_ASSERT(aIndex == ATT_CROP_RECT); Rect srcRect = aSourceRect; srcRect.Round(); if (!srcRect.ToIntRect(&mCropRect)) { mCropRect = IntRect(); } Invalidate(); } already_AddRefed FilterNodeCropSoftware::Render( const IntRect& aRect) { return GetInputDataSourceSurface(IN_CROP_IN, aRect.Intersect(mCropRect)); } void FilterNodeCropSoftware::RequestFromInputsForRect(const IntRect& aRect) { RequestInputRect(IN_CROP_IN, aRect.Intersect(mCropRect)); } IntRect FilterNodeCropSoftware::MapRectToSource(const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { return MapInputRectToSource(IN_CROP_IN, aRect.Intersect(mCropRect), aMax, aSourceNode); } IntRect FilterNodeCropSoftware::GetOutputRectInRect(const IntRect& aRect) { return GetInputRectInRect(IN_CROP_IN, aRect).Intersect(mCropRect); } int32_t FilterNodePremultiplySoftware::InputIndex(uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_PREMULTIPLY_IN: return 0; default: return -1; } } already_AddRefed 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::MapRectToSource( const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { return MapInputRectToSource(IN_PREMULTIPLY_IN, aRect, aMax, aSourceNode); } IntRect FilterNodePremultiplySoftware::GetOutputRectInRect( const IntRect& aRect) { return GetInputRectInRect(IN_PREMULTIPLY_IN, aRect); } int32_t FilterNodeUnpremultiplySoftware::InputIndex(uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_UNPREMULTIPLY_IN: return 0; default: return -1; } } already_AddRefed 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::MapRectToSource( const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { return MapInputRectToSource(IN_UNPREMULTIPLY_IN, aRect, aMax, aSourceNode); } IntRect FilterNodeUnpremultiplySoftware::GetOutputRectInRect( const IntRect& aRect) { return GetInputRectInRect(IN_UNPREMULTIPLY_IN, aRect); } void FilterNodeOpacitySoftware::SetAttribute(uint32_t aIndex, Float aValue) { MOZ_ASSERT(aIndex == ATT_OPACITY_VALUE); mValue = aValue; Invalidate(); } int32_t FilterNodeOpacitySoftware::InputIndex(uint32_t aInputEnumIndex) { switch (aInputEnumIndex) { case IN_OPACITY_IN: return 0; default: return -1; } } already_AddRefed FilterNodeOpacitySoftware::Render( const IntRect& aRect) { RefPtr input = GetInputDataSourceSurface(IN_OPACITY_IN, aRect); return input ? Opacity(input, mValue) : nullptr; } void FilterNodeOpacitySoftware::RequestFromInputsForRect(const IntRect& aRect) { RequestInputRect(IN_OPACITY_IN, aRect); } IntRect FilterNodeOpacitySoftware::MapRectToSource(const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { return MapInputRectToSource(IN_OPACITY_IN, aRect, aMax, aSourceNode); } IntRect FilterNodeOpacitySoftware::GetOutputRectInRect(const IntRect& aRect) { return GetInputRectInRect(IN_OPACITY_IN, aRect); } bool PointLightSoftware::SetAttribute(uint32_t aIndex, const Point3D& aPoint) { switch (aIndex) { case ATT_POINT_LIGHT_POSITION: mPosition = aPoint; break; default: return false; } return true; } SpotLightSoftware::SpotLightSoftware() : mSpecularFocus(0), mLimitingConeAngle(0), mLimitingConeCos(1) {} bool SpotLightSoftware::SetAttribute(uint32_t aIndex, const Point3D& aPoint) { switch (aIndex) { case ATT_SPOT_LIGHT_POSITION: mPosition = aPoint; break; case ATT_SPOT_LIGHT_POINTS_AT: mPointsAt = aPoint; break; default: return false; } return true; } bool SpotLightSoftware::SetAttribute(uint32_t aIndex, Float aValue) { switch (aIndex) { case ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE: mLimitingConeAngle = aValue; break; case ATT_SPOT_LIGHT_FOCUS: mSpecularFocus = aValue; break; default: return false; } return true; } DistantLightSoftware::DistantLightSoftware() : mAzimuth(0), mElevation(0) {} bool DistantLightSoftware::SetAttribute(uint32_t aIndex, Float aValue) { switch (aIndex) { case ATT_DISTANT_LIGHT_AZIMUTH: mAzimuth = aValue; break; case ATT_DISTANT_LIGHT_ELEVATION: mElevation = aValue; break; default: return false; } return true; } static inline Point3D Normalized(const Point3D& vec) { Point3D copy(vec); copy.Normalize(); return copy; } template FilterNodeLightingSoftware::FilterNodeLightingSoftware( const char* aTypeName) : mSurfaceScale(0) #if defined(MOZILLA_INTERNAL_API) && defined(NS_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 DeviceColor& 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, uint8_t* boundsBegin, uint8_t* boundsEnd, int32_t x, int32_t y, float surfaceScale, CoordType dx, CoordType dy) { const uint8_t* index = data + y * stride + x; CoordType zero = 0; // See this for source of constants: // http://www.w3.org/TR/SVG11/filters.html#feDiffuseLightingElement int16_t normalX = -1 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, -dx, -dy, 1, 0) + 1 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, dx, -dy, 1, 0) + -2 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, -dx, zero, 1, 0) + 2 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, dx, zero, 1, 0) + -1 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, -dx, dy, 1, 0) + 1 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, dx, dy, 1, 0); int16_t normalY = -1 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, -dx, -dy, 1, 0) + -2 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, zero, -dy, 1, 0) + -1 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, dx, -dy, 1, 0) + 1 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, -dx, dy, 1, 0) + 2 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, zero, dy, 1, 0) + 1 * ColorComponentAtPoint(index, stride, boundsBegin, boundsEnd, dx, dy, 1, 0); Point3D normal; normal.x = -surfaceScale * normalX / 4.0f; normal.y = -surfaceScale * normalY / 4.0f; normal.z = 255; return Normalized(normal); } template 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< LightType, LightingType>::RequestFromInputsForRect(const IntRect& aRect) { IntRect srcRect = aRect; srcRect.Inflate(ceil(mKernelUnitLength.width), ceil(mKernelUnitLength.height)); RequestInputRect(IN_LIGHTING_IN, srcRect); } template IntRect FilterNodeLightingSoftware::MapRectToSource( const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { IntRect srcRect = aRect; srcRect.Inflate(ceil(mKernelUnitLength.width), ceil(mKernelUnitLength.height)); return MapInputRectToSource(IN_LIGHTING_IN, srcRect, aMax, aSourceNode); } template 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); } 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* sourceBegin = sourceMap.GetData(); uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height; uint8_t* targetData = targetMap.GetData(); int32_t targetStride = targetMap.GetStride(); uint32_t lightColor = ColorToBGRA(mColor); mLight.Prepare(); mLighting.Prepare(); for (int32_t y = 0; y < size.height; y++) { for (int32_t x = 0; x < size.width; x++) { int32_t sourceIndex = y * sourceStride + x; int32_t targetIndex = y * targetStride + 4 * x; Point3D normal = GenerateNormal(sourceData, sourceStride, sourceBegin, sourceEnd, x, y, mSurfaceScale, aKernelUnitLengthX, aKernelUnitLengthY); IntPoint pointInFilterSpace(aRect.X() + x, aRect.Y() + y); Float Z = mSurfaceScale * sourceData[sourceIndex] / 255.0f; Point3D pt(pointInFilterSpace.x, pointInFilterSpace.y, Z); Point3D rayDir = mLight.GetVectorToLight(pt); uint32_t color = mLight.GetColor(lightColor, rayDir); *(uint32_t*)(targetData + targetIndex) = mLighting.LightPixel(normal, rayDir, color); } // Zero padding to keep valgrind happy. PodZero(&targetData[y * targetStride + 4 * size.width], targetStride - 4 * size.width); } return target.forget(); } DiffuseLightingSoftware::DiffuseLightingSoftware() : mDiffuseConstant(0) {} bool DiffuseLightingSoftware::SetAttribute(uint32_t aIndex, Float aValue) { switch (aIndex) { case ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT: mDiffuseConstant = aValue; break; default: return false; } return true; } uint32_t DiffuseLightingSoftware::LightPixel(const Point3D& aNormal, const Point3D& aVectorToLight, uint32_t aColor) { Float dotNL = std::max(0.0f, aNormal.DotProduct(aVectorToLight)); Float diffuseNL = mDiffuseConstant * dotNL; union { uint32_t bgra; uint8_t components[4]; } color = {aColor}; color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = umin( uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B]), 255U); color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = umin( uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G]), 255U); color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = umin( uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]), 255U); color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = 255; return color.bgra; } SpecularLightingSoftware::SpecularLightingSoftware() : mSpecularConstant(0), mSpecularExponent(0), mSpecularConstantInt(0) {} bool SpecularLightingSoftware::SetAttribute(uint32_t aIndex, Float aValue) { switch (aIndex) { case ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT: mSpecularConstant = std::min(std::max(aValue, 0.0f), 255.0f); break; case ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT: mSpecularExponent = std::min(std::max(aValue, 1.0f), 128.0f); break; default: return false; } return true; } void SpecularLightingSoftware::Prepare() { mPowCache.CacheForExponent(mSpecularExponent); mSpecularConstantInt = uint32_t(mSpecularConstant * (1 << 8)); } uint32_t SpecularLightingSoftware::LightPixel(const Point3D& aNormal, const Point3D& aVectorToLight, uint32_t aColor) { Point3D vectorToEye(0, 0, 1); Point3D halfwayVector = aVectorToLight + vectorToEye; Float halfwayLength = halfwayVector.Length(); if (halfwayLength > 0) { halfwayVector /= halfwayLength; } Float dotNH = aNormal.DotProduct(halfwayVector); uint16_t dotNHi = uint16_t(dotNH * (dotNH >= 0) * (1 << PowCache::sInputIntPrecisionBits)); // The exponent for specular is in [1,128] range, so we don't need to check // and optimize for the "default power table" scenario here. MOZ_ASSERT(mPowCache.HasPowerTable()); uint32_t specularNHi = uint32_t(mSpecularConstantInt) * mPowCache.Pow(dotNHi) >> 8; union { uint32_t bgra; uint8_t components[4]; } color = {aColor}; color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B]) >> PowCache::sOutputIntPrecisionBits, 255U); color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G]) >> PowCache::sOutputIntPrecisionBits, 255U); color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]) >> PowCache::sOutputIntPrecisionBits, 255U); color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = umax(color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B], umax(color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G], color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R])); return color.bgra; } } // namespace gfx } // namespace mozilla