summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--vcl/README.vars.md4
-rw-r--r--vcl/backendtest/outputdevice/common.cxx2
-rw-r--r--vcl/inc/skia/gdiimpl.hxx20
-rw-r--r--vcl/inc/skia/osx/gdiimpl.hxx3
-rw-r--r--vcl/inc/skia/utils.hxx61
-rw-r--r--vcl/osx/salgdiutils.cxx5
-rw-r--r--vcl/skia/gdiimpl.cxx235
-rw-r--r--vcl/skia/osx/gdiimpl.cxx65
-rw-r--r--vcl/skia/salbmp.cxx11
9 files changed, 312 insertions, 94 deletions
diff --git a/vcl/README.vars.md b/vcl/README.vars.md
index cdf356f6a2e0..7e0c3c2db0ad 100644
--- a/vcl/README.vars.md
+++ b/vcl/README.vars.md
@@ -64,3 +64,7 @@ will be used to write the log under `instdir/uitest/`.
## Kf5
* `SAL_VCL_KF5_USE_QFONT` - use `QFont` for text rendering (default for qt5, but not kf5)
+
+## Mac
+
+* `SAL_FORCE_HIDPI_SCALING` - set to 2 to fake HiDPI drawing (useful for unittests, windows may draw only top-left 1/4 of the content scaled)
diff --git a/vcl/backendtest/outputdevice/common.cxx b/vcl/backendtest/outputdevice/common.cxx
index 21a32635ab85..80408fac70fe 100644
--- a/vcl/backendtest/outputdevice/common.cxx
+++ b/vcl/backendtest/outputdevice/common.cxx
@@ -1370,7 +1370,7 @@ TestResult OutputDeviceTestCommon::checkRadialGradient(Bitmap& bitmap)
int nNumberOfErrors = 0;
// The default VCL implementation is off-center in the direction to the top-left.
// This means not all corners will be pure white => quirks.
- checkValue(pAccess, 1, 1, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 3);
+ checkValue(pAccess, 1, 1, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 2);
checkValue(pAccess, 1, 10, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
checkValue(pAccess, 10, 1, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
checkValue(pAccess, 10, 10, COL_WHITE, nNumberOfQuirks, nNumberOfErrors, 255 / 10, 255 / 5);
diff --git a/vcl/inc/skia/gdiimpl.hxx b/vcl/inc/skia/gdiimpl.hxx
index 03a4d5cf0413..70bbcf5c4dcc 100644
--- a/vcl/inc/skia/gdiimpl.hxx
+++ b/vcl/inc/skia/gdiimpl.hxx
@@ -239,6 +239,7 @@ protected:
void privateDrawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
tools::Long nHeight, double nTransparency, bool blockAA = false);
+ void privateCopyBits(const SalTwoRect& rPosAry, SkiaSalGraphicsImpl* src);
void setProvider(SalGeometryProvider* provider) { mProvider = provider; }
@@ -256,6 +257,8 @@ protected:
int GetWidth() const { return mProvider ? mProvider->GetWidth() : 1; }
// get the height of the device
int GetHeight() const { return mProvider ? mProvider->GetHeight() : 1; }
+ // Get the global HiDPI scaling factor.
+ virtual int getWindowScaling() const;
SkCanvas* getXorCanvas();
void applyXor();
@@ -277,6 +280,8 @@ protected:
// and swapping to the screen is not _that_slow.
mDirtyRect.join(addedRect);
}
+ void setCanvasScalingAndClipping();
+ void resetCanvasScalingAndClipping();
static void setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region);
sk_sp<SkImage> mergeCacheBitmaps(const SkiaSalBitmap& bitmap, const SkiaSalBitmap* alphaBitmap,
const Size targetSize);
@@ -305,9 +310,12 @@ protected:
if (graphics == nullptr)
return stream << "(null)";
// O - offscreen, G - GPU-based, R - raster
- return stream << static_cast<const void*>(graphics) << " "
- << Size(graphics->GetWidth(), graphics->GetHeight())
- << (graphics->isGPU() ? "G" : "R") << (graphics->isOffscreen() ? "O" : "");
+ stream << static_cast<const void*>(graphics) << " "
+ << Size(graphics->GetWidth(), graphics->GetHeight());
+ if (graphics->mScaling != 1)
+ stream << "*" << graphics->mScaling;
+ stream << (graphics->isGPU() ? "G" : "R") << (graphics->isOffscreen() ? "O" : "");
+ return stream;
}
SalGraphics& mParent;
@@ -318,14 +326,15 @@ protected:
// Note that mSurface may be a proxy surface and not the one from the window context.
std::unique_ptr<sk_app::WindowContext> mWindowContext;
bool mIsGPU; // whether the surface is GPU-backed
- SkIRect mDirtyRect; // the area that has been changed since the last performFlush()
+ // Note that we generally use VCL coordinates, which is not mSurface coordinates if mScaling!=1.
+ SkIRect mDirtyRect; // The area that has been changed since the last performFlush().
vcl::Region mClipRegion;
+ SkRegion mXorRegion; // The area that needs updating for the xor operation.
Color mLineColor;
Color mFillColor;
bool mXorMode;
SkBitmap mXorBitmap;
std::unique_ptr<SkCanvas> mXorCanvas;
- SkRegion mXorRegion; // the area that needs updating for the xor operation
std::unique_ptr<SkiaFlushIdle> mFlush;
// Info about pending polygons to draw (we try to merge adjacent polygons into one).
struct LastPolyPolygonInfo
@@ -336,6 +345,7 @@ protected:
};
LastPolyPolygonInfo mLastPolyPolygonInfo;
int mPendingOperationsToFlush;
+ int mScaling; // The scale factor for HiDPI screens.
};
#endif
diff --git a/vcl/inc/skia/osx/gdiimpl.hxx b/vcl/inc/skia/osx/gdiimpl.hxx
index c4892ab45b43..42a8257f8b8f 100644
--- a/vcl/inc/skia/osx/gdiimpl.hxx
+++ b/vcl/inc/skia/osx/gdiimpl.hxx
@@ -43,6 +43,9 @@ public:
virtual void Flush() override;
virtual void Flush(const tools::Rectangle&) override;
+protected:
+ virtual int getWindowScaling() const override;
+
private:
virtual void createWindowSurfaceInternal(bool forceRaster = false) override;
virtual void flushSurfaceToWindowContext() override;
diff --git a/vcl/inc/skia/utils.hxx b/vcl/inc/skia/utils.hxx
index ba479c58f234..ed404f7cc3eb 100644
--- a/vcl/inc/skia/utils.hxx
+++ b/vcl/inc/skia/utils.hxx
@@ -33,6 +33,8 @@
#include <tools/sk_app/WindowContext.h>
#include <postmac.h>
+#include <string_view>
+
namespace SkiaHelper
{
// Get the one shared GrDirectContext instance.
@@ -90,6 +92,17 @@ VCL_DLLPUBLIC const SkSurfaceProps* surfaceProps();
// Set pixel geometry to be used by SkSurfaceProps.
VCL_DLLPUBLIC void setPixelGeometry(SkPixelGeometry pixelGeometry);
+inline bool isUnitTestRunning(const char* name = nullptr)
+{
+ if (name == nullptr)
+ {
+ static const char* const testname = getenv("LO_TESTNAME");
+ return testname != nullptr;
+ }
+ const char* const testname = getenv("LO_TESTNAME");
+ return testname != nullptr && std::string_view(name) == testname;
+}
+
// Normal scaling algorithms have a poor quality when downscaling a lot.
// https://bugs.chromium.org/p/skia/issues/detail?id=11810 suggests to use mipmaps
// in such a case, which is annoying to do explicitly instead of Skia deciding which
@@ -98,11 +111,14 @@ VCL_DLLPUBLIC void setPixelGeometry(SkPixelGeometry pixelGeometry);
// Anything scaled down at least this ratio will use linear+mipmaps.
constexpr int downscaleRatioThreshold = 4;
-inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const SkMatrix& matrix)
+inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scalingType, SkMatrix matrix,
+ int scalingFactor)
{
- switch (scaling)
+ switch (scalingType)
{
case BmpScaleFlag::BestQuality:
+ if (scalingFactor != 1)
+ matrix.postScale(scalingFactor, scalingFactor);
if (matrix.getScaleX() <= 1.0 / downscaleRatioThreshold
|| matrix.getScaleY() <= 1.0 / downscaleRatioThreshold)
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
@@ -110,6 +126,7 @@ inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const SkMatri
case BmpScaleFlag::Default:
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone);
case BmpScaleFlag::Fast:
+ case BmpScaleFlag::NearestNeighbor:
return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone);
default:
assert(false);
@@ -117,12 +134,14 @@ inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const SkMatri
}
}
-inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const Size& srcSize,
- const Size& destSize)
+inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scalingType, const Size& srcSize,
+ Size destSize, int scalingFactor)
{
- switch (scaling)
+ switch (scalingType)
{
case BmpScaleFlag::BestQuality:
+ if (scalingFactor != 1)
+ destSize *= scalingFactor;
if (srcSize.Width() / destSize.Width() >= downscaleRatioThreshold
|| srcSize.Height() / destSize.Height() >= downscaleRatioThreshold)
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
@@ -130,6 +149,7 @@ inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const Size& s
case BmpScaleFlag::Default:
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone);
case BmpScaleFlag::Fast:
+ case BmpScaleFlag::NearestNeighbor:
return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone);
default:
assert(false);
@@ -137,18 +157,41 @@ inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const Size& s
}
}
-inline SkSamplingOptions makeSamplingOptions(const SalTwoRect& rPosAry)
+inline SkSamplingOptions makeSamplingOptions(const SalTwoRect& rPosAry, int scalingFactor,
+ int srcScalingFactor = 1)
{
- if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
+ // If there will be scaling, make it smooth, but not in unittests, as those often
+ // require exact color values and would be confused by this.
+ if (isUnitTestRunning())
+ return SkSamplingOptions(); // none
+ Size srcSize(rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ Size destSize(rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ if (scalingFactor != 1)
+ destSize *= scalingFactor;
+ if (srcScalingFactor != 1)
+ srcSize *= srcScalingFactor;
+ if (srcSize != destSize)
{
- if (rPosAry.mnSrcWidth / rPosAry.mnDestWidth >= downscaleRatioThreshold
- || rPosAry.mnSrcHeight / rPosAry.mnDestHeight >= downscaleRatioThreshold)
+ if (srcSize.Width() / destSize.Width() >= downscaleRatioThreshold
+ || srcSize.Height() / destSize.Height() >= downscaleRatioThreshold)
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
return SkSamplingOptions(SkCubicResampler::Mitchell()); // best
}
return SkSamplingOptions(); // none
}
+inline SkRect scaleRect(const SkRect& rect, int scaling)
+{
+ return SkRect::MakeXYWH(rect.x() * scaling, rect.y() * scaling, rect.width() * scaling,
+ rect.height() * scaling);
+}
+
+inline SkIRect scaleRect(const SkIRect& rect, int scaling)
+{
+ return SkIRect::MakeXYWH(rect.x() * scaling, rect.y() * scaling, rect.width() * scaling,
+ rect.height() * scaling);
+}
+
#ifdef DBG_UTIL
void prefillSurface(const sk_sp<SkSurface>& surface);
VCL_DLLPUBLIC void dump(const SkBitmap& bitmap, const char* file);
diff --git a/vcl/osx/salgdiutils.cxx b/vcl/osx/salgdiutils.cxx
index da1d3ab2138a..7b088864d111 100644
--- a/vcl/osx/salgdiutils.cxx
+++ b/vcl/osx/salgdiutils.cxx
@@ -64,6 +64,11 @@ float getWindowScaling()
}
bWindowScaling = true;
}
+ if( const char* env = getenv("SAL_FORCE_HIDPI_SCALING"))
+ {
+ fWindowScale = atof(env);
+ bWindowScaling = true;
+ }
}
return fWindowScale;
}
diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx
index fd86928c24c9..ebd1389c5970 100644
--- a/vcl/skia/gdiimpl.cxx
+++ b/vcl/skia/gdiimpl.cxx
@@ -286,6 +286,7 @@ SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvid
, mXorMode(false)
, mFlush(new SkiaFlushIdle(this))
, mPendingOperationsToFlush(0)
+ , mScaling(1)
{
}
@@ -304,9 +305,9 @@ void SkiaSalGraphicsImpl::createSurface()
createOffscreenSurface();
else
createWindowSurface();
- mSurface->getCanvas()->save(); // see SetClipRegion()
mClipRegion = vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight()));
mDirtyRect = SkIRect::MakeWH(GetWidth(), GetHeight());
+ setCanvasScalingAndClipping();
// We don't want to be swapping before we've painted.
mFlush->Stop();
@@ -362,7 +363,11 @@ void SkiaSalGraphicsImpl::createOffscreenSurface()
// HACK: See isOffscreen().
int width = std::max(1, GetWidth());
int height = std::max(1, GetHeight());
- mSurface = createSkSurface(width, height);
+ // We need to use window scaling even for offscreen surfaces, because the common usage is rendering something
+ // into an offscreen surface and then copy it to a window, so without scaling here the result would be originally
+ // drawn without scaling and only upscaled when drawing to a window.
+ mScaling = getWindowScaling();
+ mSurface = createSkSurface(width * mScaling, height * mScaling);
assert(mSurface);
mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
}
@@ -373,9 +378,9 @@ void SkiaSalGraphicsImpl::destroySurface()
if (mSurface)
{
// check setClipRegion() invariant
- assert(mSurface->getCanvas()->getSaveCount() == 2);
+ assert(mSurface->getCanvas()->getSaveCount() == 3);
// if this fails, something forgot to use SkAutoCanvasRestore
- assert(mSurface->getCanvas()->getTotalMatrix().isIdentity());
+ assert(mSurface->getCanvas()->getTotalMatrix() == SkMatrix::Scale(mScaling, mScaling));
}
// If we use e.g. Vulkan, we must destroy the surface before the context,
// otherwise destroying the surface will reference the context. This is
@@ -389,6 +394,7 @@ void SkiaSalGraphicsImpl::destroySurface()
mSurface.reset();
mWindowContext.reset();
mIsGPU = false;
+ mScaling = 1;
}
void SkiaSalGraphicsImpl::performFlush()
@@ -415,6 +421,8 @@ void SkiaSalGraphicsImpl::flushSurfaceToWindowContext()
assert(isGPU()); // Raster should always draw directly to backbuffer to save copying
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc); // copy as is
+ // We ignore mDirtyRect here, and mSurface already is in screenSurface coordinates,
+ // so no transformation needed.
screenSurface->getCanvas()->drawImage(makeCheckedImageSnapshot(mSurface), 0, 0,
SkSamplingOptions(), &paint);
screenSurface->flushAndSubmit(); // Otherwise the window is not drawn sometimes.
@@ -427,7 +435,10 @@ void SkiaSalGraphicsImpl::flushSurfaceToWindowContext()
// getBackbufferSurface() repeatedly. Using our own surface would duplicate
// memory and cost time copying pixels around.
assert(!isGPU());
- mWindowContext->swapBuffers(&mDirtyRect);
+ SkIRect dirtyRect = mDirtyRect;
+ if (mScaling != 1) // Adjust to mSurface coordinates if needed.
+ dirtyRect = scaleRect(dirtyRect, mScaling);
+ mWindowContext->swapBuffers(&dirtyRect);
}
}
@@ -496,7 +507,8 @@ void SkiaSalGraphicsImpl::checkSurface()
SAL_INFO("vcl.skia.trace",
"create(" << this << "): " << Size(mSurface->width(), mSurface->height()));
}
- else if (GetWidth() != mSurface->width() || GetHeight() != mSurface->height())
+ else if (GetWidth() * mScaling != mSurface->width()
+ || GetHeight() * mScaling != mSurface->height())
{
if (!avoidRecreateByResize())
{
@@ -521,7 +533,11 @@ void SkiaSalGraphicsImpl::checkSurface()
{
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc); // copy as is
+ // Scaling by current mScaling is active, undo that. We assume that the scaling
+ // does not change.
+ resetCanvasScalingAndClipping();
mSurface->getCanvas()->drawImage(snapshot, 0, 0, SkSamplingOptions(), &paint);
+ setCanvasScalingAndClipping();
}
SAL_INFO("vcl.skia.trace", "recreate(" << this << "): old " << oldSize << " new "
<< Size(mSurface->width(), mSurface->height())
@@ -550,6 +566,36 @@ void SkiaSalGraphicsImpl::flushDrawing()
mPendingOperationsToFlush = 0;
}
+void SkiaSalGraphicsImpl::setCanvasScalingAndClipping()
+{
+ SkCanvas* canvas = mSurface->getCanvas();
+ assert(canvas->getSaveCount() == 1);
+ // If HiDPI scaling is active, simply set a scaling matrix for the canvas. This means
+ // that all painting can use VCL coordinates and they'll be automatically translated to mSurface
+ // scaled coordinates. If that is not wanted, the scale() state needs to be temporarily unset.
+ // State such as mDirtyRect and mXorRegion is not scaled, the scaling matrix applies to clipping too,
+ // and the rest needs to be handled explicitly.
+ // When reading mSurface contents there's no automatic scaling and it needs to be handled explicitly.
+ canvas->save(); // keep the original state without any scaling
+ canvas->scale(mScaling, mScaling);
+
+ // SkCanvas::clipRegion() can only further reduce the clip region,
+ // but we need to set the given region, which may extend it.
+ // So handle that by always having the full clip region saved on the stack
+ // and always go back to that. SkCanvas::restore() only affects the clip
+ // and the matrix.
+ canvas->save(); // keep scaled state without clipping
+ setCanvasClipRegion(canvas, mClipRegion);
+}
+
+void SkiaSalGraphicsImpl::resetCanvasScalingAndClipping()
+{
+ SkCanvas* canvas = mSurface->getCanvas();
+ assert(canvas->getSaveCount() == 3);
+ canvas->restore(); // undo clipping
+ canvas->restore(); // undo scaling
+}
+
bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
{
if (mClipRegion == region)
@@ -560,13 +606,8 @@ bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
mClipRegion = region;
SAL_INFO("vcl.skia.trace", "setclipregion(" << this << "): " << region);
SkCanvas* canvas = mSurface->getCanvas();
- // SkCanvas::clipRegion() can only further reduce the clip region,
- // but we need to set the given region, which may extend it.
- // So handle that by always having the full clip region saved on the stack
- // and always go back to that. SkCanvas::restore() only affects the clip
- // and the matrix.
- assert(canvas->getSaveCount() == 2); // = there is just one save()
- canvas->restore();
+ assert(canvas->getSaveCount() == 3);
+ canvas->restore(); // undo previous clip state, see setCanvasScalingAndClipping()
canvas->save();
setCanvasClipRegion(canvas, region);
return true;
@@ -651,6 +692,8 @@ SkCanvas* SkiaSalGraphicsImpl::getXorCanvas()
abort();
mXorBitmap.eraseARGB(0, 0, 0, 0);
mXorCanvas = std::make_unique<SkCanvas>(mXorBitmap);
+ if (mScaling != 1)
+ mXorCanvas->scale(mScaling, mScaling);
setCanvasClipRegion(mXorCanvas.get(), mClipRegion);
}
return mXorCanvas.get();
@@ -663,6 +706,14 @@ void SkiaSalGraphicsImpl::applyXor()
// in each operation by extending mXorRegion with the area that should be
// updated.
assert(mXorMode);
+ if (mScaling != 1 && !mXorRegion.isEmpty())
+ {
+ // Scale mXorRegion to mSurface coordinates if needed.
+ std::vector<SkIRect> rects;
+ for (SkRegion::Iterator it(mXorRegion); !it.done(); it.next())
+ rects.push_back(scaleRect(it.rect(), mScaling));
+ mXorRegion.setRects(rects.data(), rects.size());
+ }
if (!mSurface || !mXorCanvas
|| !mXorRegion.op(SkIRect::MakeXYWH(0, 0, mSurface->width(), mSurface->height()),
SkRegion::kIntersect_Op))
@@ -709,8 +760,11 @@ void SkiaSalGraphicsImpl::applyXor()
}
surfaceBitmap.notifyPixelsChanged();
surfaceBitmap.setImmutable();
+ // Copy without any clipping or scaling.
+ resetCanvasScalingAndClipping();
mSurface->getCanvas()->drawImageRect(surfaceBitmap.asImage(), area, area, SkSamplingOptions(),
&paint, SkCanvas::kFast_SrcRectConstraint);
+ setCanvasScalingAndClipping();
mXorCanvas.reset();
mXorBitmap.reset();
mXorRegion.setEmpty();
@@ -766,6 +820,13 @@ void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY, Color nColor
paint.setColor(toSkColor(nColor));
// Apparently drawPixel() is actually expected to set the pixel and not draw it.
paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
+ if (mScaling != 1 && isUnitTestRunning())
+ {
+ // On HiDPI displays, draw a square on the entire non-hidpi "pixel" when running unittests,
+ // since tests often require precise pixel drawing.
+ paint.setStrokeWidth(1); // this will be scaled by mScaling
+ paint.setStrokeCap(SkPaint::kSquare_Cap);
+ }
getDrawCanvas()->drawPoint(toSkX(nX), toSkY(nY), paint);
postDraw();
}
@@ -782,6 +843,13 @@ void SkiaSalGraphicsImpl::drawLine(tools::Long nX1, tools::Long nY1, tools::Long
SkPaint paint;
paint.setColor(toSkColor(mLineColor));
paint.setAntiAlias(mParent.getAntiAlias());
+ if (mScaling != 1 && isUnitTestRunning())
+ {
+ // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
+ // smoothing that would confuse unittests.
+ paint.setStrokeWidth(1); // this will be scaled by mScaling
+ paint.setStrokeCap(SkPaint::kSquare_Cap);
+ }
getDrawCanvas()->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
postDraw();
}
@@ -812,12 +880,20 @@ void SkiaSalGraphicsImpl::privateDrawAlphaRect(tools::Long nX, tools::Long nY, t
{
paint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
paint.setStyle(SkPaint::kStroke_Style);
+ if (mScaling != 1 && isUnitTestRunning())
+ {
+ // On HiDPI displays, do not draw just a harline but instead a full-width "pixel" when running unittests,
+ // since tests often require precise pixel drawing.
+ paint.setStrokeWidth(1); // this will be scaled by mScaling
+ paint.setStrokeCap(SkPaint::kSquare_Cap);
+ }
// The obnoxious "-1 DrawRect()" hack that I don't understand the purpose of (and I'm not sure
// if anybody does), but without it some cases do not work. The max() is needed because Skia
// will not draw anything if width or height is 0.
- canvas->drawIRect(SkIRect::MakeXYWH(nX, nY, std::max(tools::Long(1), nWidth - 1),
- std::max(tools::Long(1), nHeight - 1)),
- paint);
+ canvas->drawRect(SkRect::MakeXYWH(toSkX(nX), toSkY(nY),
+ std::max(tools::Long(1), nWidth - 1),
+ std::max(tools::Long(1), nHeight - 1)),
+ paint);
}
postDraw();
}
@@ -1096,6 +1172,10 @@ bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDev
// Adjust line width for object-to-device scale.
fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
+ // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
+ // smoothing that would confuse unittests.
+ if (fLineWidth == 0 && mScaling != 1 && isUnitTestRunning())
+ fLineWidth = 1; // this will be scaled by mScaling
// Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
basegfx::B2DPolygon aPolyLine(rPolyLine);
@@ -1223,15 +1303,9 @@ void SkiaSalGraphicsImpl::copyArea(tools::Long nDestX, tools::Long nDestY, tools
SAL_INFO("vcl.skia.trace", "copyarea("
<< this << "): " << Point(nSrcX, nSrcY) << "->"
<< SkIRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
- assert(!mXorMode);
- addUpdateRegion(SkRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
// Using SkSurface::draw() should be more efficient, but it's too buggy.
- SkPaint paint;
- paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
- getDrawCanvas()->drawImageRect(makeCheckedImageSnapshot(mSurface),
- SkRect::MakeXYWH(nSrcX, nSrcY, nSrcWidth, nSrcHeight),
- SkRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight),
- SkSamplingOptions(), &paint, SkCanvas::kFast_SrcRectConstraint);
+ SalTwoRect rPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+ privateCopyBits(rPosAry, this);
postDraw();
}
@@ -1251,9 +1325,6 @@ void SkiaSalGraphicsImpl::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcG
src = this;
assert(!mXorMode);
}
- assert(!mXorMode);
- addUpdateRegion(SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
- rPosAry.mnDestHeight));
auto srcDebug = [&]() -> std::string {
if (src == this)
return "(self)";
@@ -1265,16 +1336,28 @@ void SkiaSalGraphicsImpl::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcG
}
};
SAL_INFO("vcl.skia.trace", "copybits(" << this << "): " << srcDebug() << ": " << rPosAry);
+ privateCopyBits(rPosAry, src);
+ postDraw();
+}
+
+void SkiaSalGraphicsImpl::privateCopyBits(const SalTwoRect& rPosAry, SkiaSalGraphicsImpl* src)
+{
+ assert(!mXorMode);
+ addUpdateRegion(SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight));
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
+ SkRect srcRect
+ = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ SkRect destRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight);
+ // Scaling for source coordinates must be done manually.
+ if (src->mScaling != 1)
+ srcRect = scaleRect(srcRect, src->mScaling);
// Do not use makeImageSnapshot(rect), as that one may make a needless data copy.
- getDrawCanvas()->drawImageRect(
- makeCheckedImageSnapshot(src->mSurface),
- SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight),
- SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
- rPosAry.mnDestHeight),
- makeSamplingOptions(rPosAry), &paint, SkCanvas::kFast_SrcRectConstraint);
- postDraw();
+ getDrawCanvas()->drawImageRect(makeCheckedImageSnapshot(src->mSurface), srcRect, destRect,
+ makeSamplingOptions(rPosAry, mScaling, src->mScaling), &paint,
+ SkCanvas::kFast_SrcRectConstraint);
}
bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect& rPosAry, const SalBitmap& rBitmap)
@@ -1335,7 +1418,7 @@ bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry,
// "result_alpha = 1.0 - (1.0 - floor(alpha)) * mask".
// See also blendBitmap().
- SkSamplingOptions samplingOptions = makeSamplingOptions(rPosAry);
+ SkSamplingOptions samplingOptions = makeSamplingOptions(rPosAry, mScaling);
// First do the "( 1 - alpha ) * mask"
// (no idea how to do "floor", but hopefully not needed in practice).
sk_sp<SkShader> shaderAlpha
@@ -1370,10 +1453,11 @@ void SkiaSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, const SalBitmap& r
{
assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
const SkiaSalBitmap& skiaBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
- drawShader(rPosAry,
- SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
- SkShaders::Color(toSkColor(nMaskColor)),
- skiaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry))));
+ drawShader(
+ rPosAry,
+ SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
+ SkShaders::Color(toSkColor(nMaskColor)),
+ skiaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
}
std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(tools::Long nX, tools::Long nY,
@@ -1387,9 +1471,32 @@ std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(tools::Long nX, tools:
// TODO makeImageSnapshot(rect) may copy the data, which may be a waste if this is used
// e.g. for VirtualDevice's lame alpha blending, in which case the image will eventually end up
// in blendAlphaBitmap(), where we could simply use the proper rect of the image.
- sk_sp<SkImage> image
- = makeCheckedImageSnapshot(mSurface, SkIRect::MakeXYWH(nX, nY, nWidth, nHeight));
- return std::make_shared<SkiaSalBitmap>(image);
+ sk_sp<SkImage> image = makeCheckedImageSnapshot(
+ mSurface, scaleRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), mScaling));
+ std::shared_ptr<SkiaSalBitmap> bitmap = std::make_shared<SkiaSalBitmap>(image);
+ // TODO: If the surface is scaled for HiDPI, the bitmap needs to be scaled down, otherwise
+ // it would have incorrect size from the API point of view. This could lead to loss of quality
+ // if the bitmap is drawn to another scaled surface. Since the bitmap scaling is done only
+ // on-demand, this state should be detected when drawing the bitmap and the scaling
+ // should be ignored.
+ if (mScaling != 1)
+ {
+ if (!isUnitTestRunning())
+ bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::BestQuality);
+ else
+ {
+ // Some tests require exact pixel values and would be confused by smooth-scaling.
+ // And some draw something smooth and not smooth-scaling there would break the checks.
+ if (isUnitTestRunning("BackendTest__testDrawHaflEllipseAAWithPolyLineB2D_")
+ || isUnitTestRunning("BackendTest__testDrawRectAAWithLine_"))
+ {
+ bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::BestQuality);
+ }
+ else
+ bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::NearestNeighbor);
+ }
+ }
+ return bitmap;
}
Color SkiaSalGraphicsImpl::getPixel(tools::Long nX, tools::Long nY)
@@ -1400,11 +1507,11 @@ Color SkiaSalGraphicsImpl::getPixel(tools::Long nX, tools::Long nY)
flushDrawing();
// This is presumably slow, but getPixel() should be generally used only by unit tests.
SkBitmap bitmap;
- if (!bitmap.tryAllocN32Pixels(GetWidth(), GetHeight()))
+ if (!bitmap.tryAllocN32Pixels(mSurface->width(), mSurface->height()))
abort();
if (!mSurface->readPixels(bitmap, 0, 0))
abort();
- return fromSkColor(bitmap.getColor(nX, nY));
+ return fromSkColor(bitmap.getColor(nX * mScaling, nY * mScaling));
}
void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFlags)
@@ -1499,6 +1606,7 @@ sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitma
const SkiaSalBitmap* alphaBitmap,
const Size targetSize)
{
+ // TODO This should take into account mScaling!=1, and callers should use that too.
sk_sp<SkImage> image;
if (targetSize.IsEmpty())
return image;
@@ -1574,7 +1682,7 @@ sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitma
matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / bitmap.GetSize().Width());
matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / bitmap.GetSize().Height());
canvas->concat(matrix);
- samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix);
+ samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix, 1);
}
if (alphaBitmap != nullptr)
{
@@ -1625,11 +1733,11 @@ bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBi
else if (rSkiaAlphaBitmap.IsFullyOpaqueAsAlpha()) // alpha can be ignored
drawBitmap(rPosAry, rSkiaSourceBitmap);
else
- drawShader(
- rPosAry,
- SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
- rSkiaSourceBitmap.GetSkShader(makeSamplingOptions(rPosAry)),
- rSkiaAlphaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry))));
+ drawShader(rPosAry,
+ SkShaders::Blend(
+ SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
+ rSkiaSourceBitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)),
+ rSkiaAlphaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
return true;
}
@@ -1638,7 +1746,7 @@ void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBit
{
if (bitmap.PreferSkShader())
{
- drawShader(rPosAry, bitmap.GetSkShader(makeSamplingOptions(rPosAry)), blendMode);
+ drawShader(rPosAry, bitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)), blendMode);
return;
}
// Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
@@ -1678,7 +1786,7 @@ void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkIma
"drawimage(" << this << "): " << rPosAry << ":" << SkBlendMode_Name(eBlendMode));
addUpdateRegion(aDestinationRect);
getDrawCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect,
- makeSamplingOptions(rPosAry), &aPaint,
+ makeSamplingOptions(rPosAry, mScaling), &aPaint,
SkCanvas::kFast_SrcRectConstraint);
++mPendingOperationsToFlush; // tdf#136369
postDraw();
@@ -1805,8 +1913,8 @@ bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
SkAutoCanvasRestore autoRestore(canvas, true);
canvas->concat(matrix);
SkSamplingOptions samplingOptions;
- if (matrixNeedsHighQuality(matrix))
- samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix);
+ if (matrixNeedsHighQuality(matrix) || (mScaling != 1 && !isUnitTestRunning()))
+ samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix, mScaling);
if (fAlpha == 1.0)
canvas->drawImage(imageToDraw, 0, 0, samplingOptions);
else
@@ -1832,8 +1940,8 @@ bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
SkAutoCanvasRestore autoRestore(canvas, true);
canvas->concat(matrix);
SkSamplingOptions samplingOptions;
- if (matrixNeedsHighQuality(matrix))
- samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix);
+ if (matrixNeedsHighQuality(matrix) || (mScaling != 1 && !isUnitTestRunning()))
+ samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix, mScaling);
if (pSkiaAlphaBitmap)
{
SkPaint paint;
@@ -2059,6 +2167,21 @@ bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
}
}
+static int getScaling()
+{
+ // It makes sense to support the debugging flag on all platforms
+ // for unittests purpose, even if the actual windows cannot do it.
+ if (const char* env = getenv("SAL_FORCE_HIDPI_SCALING"))
+ return atoi(env);
+ return 1;
+}
+
+int SkiaSalGraphicsImpl::getWindowScaling() const
+{
+ static const int scaling = getScaling();
+ return scaling;
+}
+
#ifdef DBG_UTIL
void SkiaSalGraphicsImpl::dump(const char* file) const
{
diff --git a/vcl/skia/osx/gdiimpl.cxx b/vcl/skia/osx/gdiimpl.cxx
index f4e2a63bee80..73e5e09d20d0 100644
--- a/vcl/skia/osx/gdiimpl.cxx
+++ b/vcl/skia/osx/gdiimpl.cxx
@@ -58,13 +58,14 @@ void AquaSkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster)
displayParams.fColorType = kN32_SkColorType;
sk_app::window_context_factory::MacWindowInfo macWindow;
macWindow.fMainView = mrShared.mpFrame->mpNSView;
+ mScaling = getWindowScaling();
RenderMethod renderMethod = forceRaster ? RenderRaster : renderMethodToUse();
switch (renderMethod)
{
case RenderRaster:
// RasterWindowContext_mac uses OpenGL internally, which we don't want,
// so use our own surface and do blitting to the screen ourselves.
- mSurface = createSkSurface(GetWidth(), GetHeight());
+ mSurface = createSkSurface(GetWidth() * mScaling, GetHeight() * mScaling);
break;
case RenderMetal:
mWindowContext
@@ -74,7 +75,7 @@ void AquaSkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster)
// it appears that Metal surfaces cannot be read from, which would break things
// like copyArea().
if (mWindowContext)
- mSurface = createSkSurface(GetWidth(), GetHeight());
+ mSurface = createSkSurface(GetWidth() * mScaling, GetHeight() * mScaling);
break;
case RenderVulkan:
abort();
@@ -82,6 +83,12 @@ void AquaSkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster)
}
}
+int AquaSkiaSalGraphicsImpl::getWindowScaling() const
+{
+ // The system function returns float, but only integer multiples realistically make sense.
+ return sal::aqua::getWindowScaling();
+}
+
void AquaSkiaSalGraphicsImpl::Flush() { performFlush(); }
void AquaSkiaSalGraphicsImpl::Flush(const tools::Rectangle&) { performFlush(); }
@@ -125,36 +132,54 @@ void AquaSkiaSalGraphicsImpl::flushSurfaceToScreenCG()
SkPixmap pixmap;
if (!image->peekPixels(&pixmap))
abort();
+ // If window scaling, then mDirtyRect is in VCL coordinates, mSurface has screen size (=points,HiDPI),
+ // maContextHolder has screen size but a scale matrix set so its inputs are in VCL coordinates (see
+ // its setup in AquaSharedAttributes::checkContext()).
// This creates the bitmap context from the cropped part, writable_addr32() will get
// the first pixel of mDirtyRect.topLeft(), and using pixmap.rowBytes() ensures the following
// pixel lines will be read from correct positions.
CGContextRef context = CGBitmapContextCreate(
- pixmap.writable_addr32(mDirtyRect.x(), mDirtyRect.y()), mDirtyRect.width(),
- mDirtyRect.height(), 8, pixmap.rowBytes(), GetSalData()->mxRGBSpace,
- toCGBitmapType(image->colorType(), image->alphaType()));
- assert(context); // TODO
+ pixmap.writable_addr32(mDirtyRect.x() * mScaling, mDirtyRect.y() * mScaling),
+ mDirtyRect.width() * mScaling, mDirtyRect.height() * mScaling, 8, pixmap.rowBytes(),
+ GetSalData()->mxRGBSpace, toCGBitmapType(image->colorType(), image->alphaType()));
+ if (!context)
+ {
+ SAL_WARN("vcl.skia", "flushSurfaceToScreenGC(): Failed to allocate bitmap context");
+ return;
+ }
CGImageRef screenImage = CGBitmapContextCreateImage(context);
- assert(screenImage); // TODO
- if (mrShared.isFlipped())
+ if (!screenImage)
{
- const CGRect screenRect
- = CGRectMake(mDirtyRect.x(), GetHeight() - mDirtyRect.y() - mDirtyRect.height(),
- mDirtyRect.width(), mDirtyRect.height());
- mrShared.maContextHolder.saveState();
- CGContextTranslateCTM(mrShared.maContextHolder.get(), 0, pixmap.height());
- CGContextScaleCTM(mrShared.maContextHolder.get(), 1, -1);
- CGContextDrawImage(mrShared.maContextHolder.get(), screenRect, screenImage);
- mrShared.maContextHolder.restoreState();
+ CGContextRelease(context);
+ SAL_WARN("vcl.skia", "flushSurfaceToScreenGC(): Failed to allocate screen image");
+ return;
}
- else
+ mrShared.maContextHolder.saveState();
+ // Drawing to the actual window has scaling active, so use unscaled coordinates, the scaling matrix will scale them
+ // to the proper screen coordinates. Unless the scaling is fake for debugging, in which case scale them to draw
+ // at the scaled size.
+ int windowScaling = 1;
+ static const char* env = getenv("SAL_FORCE_HIDPI_SCALING");
+ if (env != nullptr)
+ windowScaling = atoi(env);
+ CGRect drawRect
+ = CGRectMake(mDirtyRect.x() * windowScaling, mDirtyRect.y() * windowScaling,
+ mDirtyRect.width() * windowScaling, mDirtyRect.height() * windowScaling);
+ if (mrShared.isFlipped())
{
- const CGRect screenRect
- = CGRectMake(mDirtyRect.x(), mDirtyRect.y(), mDirtyRect.width(), mDirtyRect.height());
- CGContextDrawImage(mrShared.maContextHolder.get(), screenRect, screenImage);
+ // I don't understand why, but apparently it's needed to explicitly to flip the drawing, even though maContextHelper
+ // has this set up, so this unsets the flipping.
+ CGFloat invertedY = drawRect.origin.y + drawRect.size.height;
+ CGContextTranslateCTM(mrShared.maContextHolder.get(), 0, invertedY);
+ CGContextScaleCTM(mrShared.maContextHolder.get(), 1, -1);
+ drawRect.origin.y = 0;
}
+ CGContextDrawImage(mrShared.maContextHolder.get(), drawRect, screenImage);
+ mrShared.maContextHolder.restoreState();
CGImageRelease(screenImage);
CGContextRelease(context);
+ // This is also in VCL coordinates.
mrShared.refreshRect(mDirtyRect.x(), mDirtyRect.y(), mDirtyRect.width(), mDirtyRect.height());
}
diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx
index 31a369724259..c064f00ad565 100644
--- a/vcl/skia/salbmp.cxx
+++ b/vcl/skia/salbmp.cxx
@@ -422,6 +422,11 @@ bool SkiaSalBitmap::Scale(const double& rScaleX, const double& rScaleY, BmpScale
case BmpScaleFlag::Fast:
mScaleQuality = nScaleFlag;
break;
+ case BmpScaleFlag::NearestNeighbor:
+ // We handle this the same way as Fast by mapping to Skia's nearest-neighbor,
+ // and it's needed for unittests (mScaling and testTdf132367()).
+ mScaleQuality = nScaleFlag;
+ break;
case BmpScaleFlag::Default:
if (mScaleQuality == BmpScaleFlag::BestQuality)
mScaleQuality = nScaleFlag;
@@ -781,7 +786,7 @@ const sk_sp<SkImage>& SkiaSalBitmap::GetSkImage() const
paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
surface->getCanvas()->drawImageRect(
mImage, SkRect::MakeWH(mSize.Width(), mSize.Height()),
- makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize), &paint);
+ makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize, 1), &paint);
SAL_INFO("vcl.skia.trace", "getskimage(" << this << "): image scaled "
<< Size(mImage->width(), mImage->height())
<< "->" << mSize << ":"
@@ -893,7 +898,7 @@ const sk_sp<SkImage>& SkiaSalBitmap::GetAlphaSkImage() const
paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
surface->getCanvas()->drawImageRect(
mImage, SkRect::MakeWH(mSize.Width(), mSize.Height()),
- scaling ? makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize)
+ scaling ? makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize, 1)
: SkSamplingOptions(),
&paint);
if (scaling)
@@ -1149,7 +1154,7 @@ void SkiaSalBitmap::EnsureBitmapData()
if (imageSize(mImage) != mSize) // pending scaling?
{
canvas.drawImageRect(mImage, SkRect::MakeWH(mSize.getWidth(), mSize.getHeight()),
- makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize),
+ makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize, 1),
&paint);
SAL_INFO("vcl.skia.trace",
"ensurebitmapdata(" << this << "): image scaled " << imageSize(mImage) << "->"