Reimplement fix for tdf#156629 and tdf#156630
Reimplement fix in commit 926c5246b6694d469a6caed5d7ea4c3a68648468
in an attempt to reduce the fix's performance hit. Instead of
invoking EnsureBitmapData() and converting the SkImage to a
memory buffer, any pending scaling on alpha masks is handled by
creating a new, scaled SkImage.
Note that in commit 926c5246b6694d469a6caed5d7ea4c3a68648468,
EnsureBitmapData() was invoked which converted the SkImage to a
memory buffer. That in turn would make Invert() a noop which
fixed the related bug where an image has been opened and, before
it has been printed or run in a slideshow, the alpha mask would
unexpectedly be inverted. So keep this immutable behavior without
calling EnsureBitmapData() by adding a new mutability flag.
Change-Id: I99dc272b40c53664ea49333402a6a637b1548a5f
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/155850
Tested-by: Jenkins
Reviewed-by: Patrick Luby <plubius@neooffice.org>
diff --git a/vcl/inc/skia/salbmp.hxx b/vcl/inc/skia/salbmp.hxx
index 3c8b1a9..c42aa30 100644
--- a/vcl/inc/skia/salbmp.hxx
+++ b/vcl/inc/skia/salbmp.hxx
@@ -211,6 +211,7 @@ private:
boost::shared_ptr<sal_uInt8[]> mBuffer;
int mScanlineSize; // size of one row in mBuffer (based on mPixelsSize)
sk_sp<SkImage> mImage; // possibly GPU-backed
bool mImageImmutable = false;
sk_sp<SkImage> mAlphaImage; // cached contents as alpha image, possibly GPU-backed
// Actual scaling triggered by scale() is done on-demand. This is the size of the pixel
// data in mBuffer, if it differs from mSize, then there is a scaling operation pending.
diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx
index eac9136..2aa9a0a 100644
--- a/vcl/skia/salbmp.cxx
+++ b/vcl/skia/salbmp.cxx
@@ -164,6 +164,7 @@ bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelF
ResetAllData();
const SkiaSalBitmap& src = static_cast<const SkiaSalBitmap&>(rSalBmp);
mImage = src.mImage;
mImageImmutable = src.mImageImmutable;
mAlphaImage = src.mAlphaImage;
mBuffer = src.mBuffer;
mPalette = src.mPalette;
@@ -224,7 +225,7 @@ BitmapBuffer* SkiaSalBitmap::AcquireBuffer(BitmapAccessMode nMode)
assert(!mEraseColorSet);
break;
case BitmapAccessMode::Info:
// Related tdf#156630 and tdf#156629 force snapshot of alpha mask
// Related tdf#156629 and tdf#156630 force snapshot of alpha mask
// On macOS, with Skia/Metal or Skia/Raster with a Retina display
// (i.e. 2.0 window scale), the alpha mask gets upscaled in certain
// cases.
@@ -236,9 +237,24 @@ BitmapBuffer* SkiaSalBitmap::AcquireBuffer(BitmapAccessMode nMode)
// fail:
// https://bugs.documentfoundation.org/attachment.cgi?id=188792
static const bool bForceHiDPIScaling = getenv("SAL_FORCE_HIDPI_SCALING") != nullptr;
if (mPixelsSize != mSize || (bForceHiDPIScaling && mImage))
EnsureBitmapData();
assert(mPixelsSize == mSize);
if (mImage && !mImageImmutable && mBitCount == 8 && mPalette.IsGreyPalette8Bit()
&& (mPixelsSize != mSize || bForceHiDPIScaling))
{
ResetToSkImage(GetSkImage());
ResetPendingScaling();
assert(mPixelsSize == mSize);
// When many of the images affected by tdf#156629 and
// tdf#156630 are exported to PDF the first time after the
// image has been opened and before it has been printed or run
// in a slideshow, the alpha mask will unexpectedly be
// inverted. Fix that by marking this alpha mask as immutable
// so that when Invert() is called on this alpha mask, it will
// be a noop. Invert() is a noop after EnsureBitmapData() is
// called but we don't want to call that due to performance
// so set a flag instead.
mImageImmutable = true;
}
break;
}
#ifdef DBG_UTIL
@@ -653,17 +669,18 @@ bool SkiaSalBitmap::Invert()
// Normally this would need to convert contents of mBuffer for all possible formats,
// so just let the VCL algorithm do it.
// Avoid the costly SkImage->buffer->SkImage conversion.
if (!mBuffer && mImage && !mEraseColorSet)
if (!mBuffer && mImage && !mImageImmutable && !mEraseColorSet)
{
// This is 8-bit bitmap serving as alpha/transparency/mask, so the image itself needs no alpha.
sk_sp<SkSurface> surface = createSkSurface(mSize, kOpaque_SkAlphaType);
// Use pixels size so that we don't downscale the image
sk_sp<SkSurface> surface = createSkSurface(mPixelsSize, kOpaque_SkAlphaType);
surface->getCanvas()->clear(SK_ColorWHITE);
SkPaint paint;
paint.setBlendMode(SkBlendMode::kDifference);
// Drawing the image does not work so create a shader from the image
paint.setShader(GetSkShader(SkSamplingOptions()));
surface->getCanvas()->drawRect(SkRect::MakeXYWH(0, 0, mSize.Width(), mSize.Height()),
paint);
surface->getCanvas()->drawRect(
SkRect::MakeXYWH(0, 0, mPixelsSize.Width(), mPixelsSize.Height()), paint);
ResetToSkImage(makeCheckedImageSnapshot(surface));
DataChanged();
SAL_INFO("vcl.skia.trace", "invert(" << this << ")");
@@ -1351,6 +1368,7 @@ void SkiaSalBitmap::ResetToBuffer()
// This should never be called to drop mImage if that's the only data we have.
assert(mBuffer || !mImage);
mImage.reset();
mImageImmutable = false;
mAlphaImage.reset();
mEraseColorSet = false;
}
@@ -1360,6 +1378,7 @@ void SkiaSalBitmap::ResetToSkImage(sk_sp<SkImage> image)
assert(mReadAccessCount == 0); // can't reset mBuffer if there's a read access pointing to it
SkiaZone zone;
mBuffer.reset();
// Just to be safe, assume mutability of the image does not change
mImage = image;
mAlphaImage.reset();
mEraseColorSet = false;
@@ -1371,6 +1390,7 @@ void SkiaSalBitmap::ResetAllData()
SkiaZone zone;
mBuffer.reset();
mImage.reset();
mImageImmutable = false;
mAlphaImage.reset();
mEraseColorSet = false;
mPixelsSize = mSize;
@@ -1391,7 +1411,10 @@ void SkiaSalBitmap::ResetPendingScaling()
// Information about the pending scaling has been discarded, so make sure we do not
// keep around any cached images that would still need scaling.
if (mImage && imageSize(mImage) != mSize)
{
mImage.reset();
mImageImmutable = false;
}
if (mAlphaImage && imageSize(mAlphaImage) != mSize)
mAlphaImage.reset();
}