diff --git a/gfx/2d/DrawTargetSkia.cpp b/gfx/2d/DrawTargetSkia.cpp index 2e349962e3a0..3a95ee2d6772 100644 --- a/gfx/2d/DrawTargetSkia.cpp +++ b/gfx/2d/DrawTargetSkia.cpp @@ -24,6 +24,7 @@ #include "Logging.h" #include "Tools.h" #include "DataSurfaceHelpers.h" +#include "PathHelpers.h" #include "Swizzle.h" #include @@ -805,19 +806,99 @@ DrawTargetSkia::Stroke(const Path *aPath, mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint); } +static Float +DashPeriodLength(const StrokeOptions& aStrokeOptions) +{ + Float length = 0; + for (size_t i = 0; i < aStrokeOptions.mDashLength; i++) { + length += aStrokeOptions.mDashPattern[i]; + } + if (aStrokeOptions.mDashLength & 1) { + // "If an odd number of values is provided, then the list of values is + // repeated to yield an even number of values." + // Double the length. + length += length; + } + return length; +} + +static inline Float +RoundDownToMultiple(Float aValue, Float aFactor) +{ + return floorf(aValue / aFactor) * aFactor; +} + +static Rect +UserSpaceStrokeClip(const IntRect &aDeviceClip, + const Matrix &aTransform, + const StrokeOptions &aStrokeOptions) +{ + Matrix inverse = aTransform; + if (!inverse.Invert()) { + return Rect(); + } + Rect deviceClip(aDeviceClip); + deviceClip.Inflate(MaxStrokeExtents(aStrokeOptions, aTransform)); + return inverse.TransformBounds(deviceClip); +} + +static Rect +ShrinkClippedStrokedRect(const Rect &aStrokedRect, const IntRect &aDeviceClip, + const Matrix &aTransform, + const StrokeOptions &aStrokeOptions) +{ + Rect userSpaceStrokeClip = + UserSpaceStrokeClip(aDeviceClip, aTransform, aStrokeOptions); + + Rect intersection = aStrokedRect.Intersect(userSpaceStrokeClip); + Float dashPeriodLength = DashPeriodLength(aStrokeOptions); + if (intersection.IsEmpty() || dashPeriodLength == 0.0f) { + return intersection; + } + + // Reduce the rectangle side lengths in multiples of the dash period length + // so that the visible dashes stay in the same place. + Margin insetBy = aStrokedRect - intersection; + insetBy.top = RoundDownToMultiple(insetBy.top, dashPeriodLength); + insetBy.right = RoundDownToMultiple(insetBy.right, dashPeriodLength); + insetBy.bottom = RoundDownToMultiple(insetBy.bottom, dashPeriodLength); + insetBy.left = RoundDownToMultiple(insetBy.left, dashPeriodLength); + + Rect shrunkRect = aStrokedRect; + shrunkRect.Deflate(insetBy); + return shrunkRect; +} + void DrawTargetSkia::StrokeRect(const Rect &aRect, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aOptions) { + // Stroking large rectangles with dashes is expensive with Skia (fixed + // overhead based on the number of dashes, regardless of whether the dashes + // are visible), so we try to reduce the size of the stroked rectangle as + // much as possible before passing it on to Skia. + Rect rect = aRect; + if (aStrokeOptions.mDashLength > 0 && !rect.IsEmpty()) { + IntRect deviceClip(IntPoint(0, 0), mSize); + SkIRect clipBounds; + if (mCanvas->getClipDeviceBounds(&clipBounds)) { + deviceClip = SkIRectToIntRect(clipBounds); + } + rect = ShrinkClippedStrokedRect(rect, deviceClip, mTransform, aStrokeOptions); + if (rect.IsEmpty()) { + return; + } + } + MarkChanged(); AutoPaintSetup paint(mCanvas.get(), aOptions, aPattern); if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) { return; } - mCanvas->drawRect(RectToSkRect(aRect), paint.mPaint); + mCanvas->drawRect(RectToSkRect(rect), paint.mPaint); } void diff --git a/gfx/2d/HelpersSkia.h b/gfx/2d/HelpersSkia.h index 95c67ad05925..3de2a56048b8 100644 --- a/gfx/2d/HelpersSkia.h +++ b/gfx/2d/HelpersSkia.h @@ -297,6 +297,12 @@ IntRectToSkIRect(const IntRect& aRect) return SkIRect::MakeXYWH(aRect.x, aRect.y, aRect.width, aRect.height); } +static inline IntRect +SkIRectToIntRect(const SkIRect& aRect) +{ + return IntRect(aRect.x(), aRect.y(), aRect.width(), aRect.height()); +} + static inline Point SkPointToPoint(const SkPoint &aPoint) {