summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuboš Luňák <l.lunak@collabora.com>2020-01-14 16:30:59 +0100
committerLuboš Luňák <l.lunak@collabora.com>2020-01-16 16:33:59 +0100
commitdfe106c8a939ef051135bb6d546ba2d50afa766e (patch)
treefc0b256fb97b7f168ccf5c357eb42a84b32b29f4
parentb1d3ef798a89d11b853c467fa9ce0fe6ed235735 (diff)
implement xor drawing for Skia
Fortunately it seems this is largely unused (I can see 5 invocations when running all LO tests), so I went for the crude approach of redirecting all drawing to a temporary bitmap and then manually xor-ing all the data after each draw operation. This could be optimized if needed. Change-Id: I6fc91362dd93188775b371d5548a68a58645f85c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/86776 Tested-by: Jenkins Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
-rw-r--r--vcl/inc/skia/gdiimpl.hxx8
-rw-r--r--vcl/qa/cppunit/BackendTest.cxx13
-rw-r--r--vcl/skia/gdiimpl.cxx108
3 files changed, 104 insertions, 25 deletions
diff --git a/vcl/inc/skia/gdiimpl.hxx b/vcl/inc/skia/gdiimpl.hxx
index d54c09d67eb8..fd7cafc387db 100644
--- a/vcl/inc/skia/gdiimpl.hxx
+++ b/vcl/inc/skia/gdiimpl.hxx
@@ -206,6 +206,8 @@ protected:
void preDraw();
// To be called after any drawing.
void postDraw();
+ // The canvas to drawn to. Will be diverted to a temporary for Xor mode.
+ SkCanvas* getDrawCanvas() { return mXorMode ? getXorCanvas() : mSurface->getCanvas(); }
virtual void createSurface();
// Call to ensure that mSurface is valid. If mSurface is going to be modified,
@@ -240,6 +242,9 @@ protected:
void drawMask(const SalTwoRect& rPosAry, const sk_sp<SkImage>& rImage, Color nMaskColor);
+ SkCanvas* getXorCanvas();
+ static void setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region);
+
// When drawing using GPU, rounding errors may result in off-by-one errors,
// see https://bugs.chromium.org/p/skia/issues/detail?id=9611 . Compensate for
// it by using centers of pixels (Skia uses float coordinates). In raster case
@@ -267,6 +272,9 @@ protected:
vcl::Region mClipRegion;
Color mLineColor;
Color mFillColor;
+ bool mXorMode;
+ SkBitmap mXorBitmap;
+ std::unique_ptr<SkCanvas> mXorCanvas;
std::unique_ptr<SkiaFlushIdle> mFlush;
};
diff --git a/vcl/qa/cppunit/BackendTest.cxx b/vcl/qa/cppunit/BackendTest.cxx
index 07c6d03d2a0c..802e4eb239c6 100644
--- a/vcl/qa/cppunit/BackendTest.cxx
+++ b/vcl/qa/cppunit/BackendTest.cxx
@@ -430,7 +430,17 @@ public:
BitmapEx aBitmapEx = aOutDevTest.setupDrawBlend();
auto eResult = vcl::test::OutputDeviceTestBitmap::checkBlend(aBitmapEx);
exportImage("08-05_blend_test.png", aBitmapEx);
- if (aOutDevTest.getRenderBackendName() == "skia")
+ if (SHOULD_ASSERT)
+ CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
+ }
+
+ void testDrawXor()
+ {
+ vcl::test::OutputDeviceTestAnotherOutDev aOutDevTest;
+ Bitmap aBitmap = aOutDevTest.setupXOR();
+ auto eResult = vcl::test::OutputDeviceTestAnotherOutDev::checkXOR(aBitmap);
+ exportImage("08-06_xor_test.png", aBitmap);
+ if (SHOULD_ASSERT)
CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
}
@@ -475,6 +485,7 @@ public:
CPPUNIT_TEST(testDrawBitmapExWithAlpha);
CPPUNIT_TEST(testDrawMask);
CPPUNIT_TEST(testDrawBlend);
+ CPPUNIT_TEST(testDrawXor);
CPPUNIT_TEST_SUITE_END();
};
diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx
index 2fff327b37b1..7acd7e20e413 100644
--- a/vcl/skia/gdiimpl.cxx
+++ b/vcl/skia/gdiimpl.cxx
@@ -175,6 +175,7 @@ SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvid
, mIsGPU(false)
, mLineColor(SALCOLOR_NONE)
, mFillColor(SALCOLOR_NONE)
+ , mXorMode(false)
, mFlush(new SkiaFlushIdle(this))
{
}
@@ -310,6 +311,48 @@ void SkiaSalGraphicsImpl::preDraw() { checkSurface(); }
void SkiaSalGraphicsImpl::postDraw()
{
+ if (mXorMode)
+ {
+ // Apply the result from the temporary bitmap manually. This is indeed
+ // slow, but it doesn't seem to be needed often. It could be optimized
+ // by knowing the bounds of the xor operation, if needed.
+ SAL_INFO("vcl.skia",
+ "applyxor(" << this << "): " << Size(mSurface->width(), mSurface->height()));
+ // Copy the surface contents to another pixmap.
+ SkBitmap surfaceBitmap;
+ // Use unpremultiplied alpha format, so that we do not have to do the conversions to get
+ // the RGB and back (Skia will do it when converting, but it'll be presumably faster at it).
+ if (!surfaceBitmap.tryAllocPixels(
+ mSurface->imageInfo().makeAlphaType(kUnpremul_SkAlphaType)))
+ abort();
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc); // copy as is
+ SkCanvas canvas(surfaceBitmap);
+ canvas.drawImage(mSurface->makeImageSnapshot(), 0, 0, &paint);
+ // xor to surfaceBitmap
+ assert(surfaceBitmap.info().alphaType() == kUnpremul_SkAlphaType);
+ assert(mXorBitmap.info().alphaType() == kUnpremul_SkAlphaType);
+ assert(surfaceBitmap.bytesPerPixel() == 4);
+ assert(mXorBitmap.bytesPerPixel() == 4);
+ for (int y = 0; y < surfaceBitmap.height(); ++y)
+ {
+ uint8_t* data = static_cast<uint8_t*>(surfaceBitmap.getAddr(0, y));
+ const uint8_t* xordata = static_cast<uint8_t*>(mXorBitmap.getAddr(0, y));
+ for (int x = 0; x < surfaceBitmap.width(); ++x)
+ {
+ *data++ ^= *xordata++;
+ *data++ ^= *xordata++;
+ *data++ ^= *xordata++;
+ // alpha is not xor-ed
+ data++;
+ xordata++;
+ }
+ }
+ surfaceBitmap.notifyPixelsChanged();
+ mSurface->getCanvas()->drawBitmap(surfaceBitmap, 0, 0, &paint);
+ mXorCanvas.reset();
+ mXorBitmap.reset();
+ }
if (!isOffscreen())
{
if (!Application::IsInExecute())
@@ -359,6 +402,12 @@ bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
assert(canvas->getSaveCount() == 2); // = there is just one save()
canvas->restore();
canvas->save();
+ setCanvasClipRegion(canvas, region);
+ return true;
+}
+
+void SkiaSalGraphicsImpl::setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region)
+{
SkPath path;
// Handle polygons last, since rectangle->polygon area conversions
// are problematic (see addPolygonToPath() comment).
@@ -376,7 +425,6 @@ bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
}
path.setFillType(SkPathFillType::kEvenOdd);
canvas->clipPath(path);
- return true;
}
void SkiaSalGraphicsImpl::ResetClipRegion()
@@ -398,9 +446,24 @@ void SkiaSalGraphicsImpl::SetFillColor() { mFillColor = SALCOLOR_NONE; }
void SkiaSalGraphicsImpl::SetFillColor(Color nColor) { mFillColor = nColor; }
-void SkiaSalGraphicsImpl::SetXORMode(bool, bool)
+void SkiaSalGraphicsImpl::SetXORMode(bool set, bool) { mXorMode = set; }
+
+SkCanvas* SkiaSalGraphicsImpl::getXorCanvas()
{
- // TODO
+ assert(mXorMode);
+ // Skia does not implement xor drawing, so we need to handle it manually by redirecting
+ // to a temporary SkBitmap and then doing the xor operation on the data ourselves.
+ // There's no point in using SkSurface for GPU, we'd immediately need to get the pixels back.
+ if (!mXorCanvas)
+ {
+ // Use unpremultiplied alpha (see xor applying in PostDraw()).
+ if (!mXorBitmap.tryAllocPixels(mSurface->imageInfo().makeAlphaType(kUnpremul_SkAlphaType)))
+ abort();
+ mXorBitmap.eraseARGB(0, 0, 0, 0);
+ mXorCanvas = std::make_unique<SkCanvas>(mXorBitmap);
+ setCanvasClipRegion(mXorCanvas.get(), mClipRegion);
+ }
+ return mXorCanvas.get();
}
void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor)
@@ -443,12 +506,11 @@ void SkiaSalGraphicsImpl::drawPixel(long nX, long nY, Color nColor)
return;
preDraw();
SAL_INFO("vcl.skia", "drawpixel(" << this << "): " << Point(nX, nY) << ":" << nColor);
- SkCanvas* canvas = mSurface->getCanvas();
SkPaint paint;
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
- canvas->drawPoint(toSkX(nX), toSkY(nY), paint);
+ getDrawCanvas()->drawPoint(toSkX(nX), toSkY(nY), paint);
postDraw();
}
@@ -459,11 +521,10 @@ void SkiaSalGraphicsImpl::drawLine(long nX1, long nY1, long nX2, long nY2)
preDraw();
SAL_INFO("vcl.skia", "drawline(" << this << "): " << Point(nX1, nY1) << "->" << Point(nX2, nY2)
<< ":" << mLineColor);
- SkCanvas* canvas = mSurface->getCanvas();
SkPaint paint;
paint.setColor(toSkColor(mLineColor));
paint.setAntiAlias(mParent.getAntiAliasB2DDraw());
- canvas->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
+ getDrawCanvas()->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
postDraw();
}
@@ -474,7 +535,7 @@ void SkiaSalGraphicsImpl::privateDrawAlphaRect(long nX, long nY, long nWidth, lo
SAL_INFO("vcl.skia", "privatedrawrect(" << this << "): " << Point(nX, nY) << "/"
<< Size(nWidth, nHeight) << ":" << mLineColor << ":"
<< mFillColor << ":" << fTransparency);
- SkCanvas* canvas = mSurface->getCanvas();
+ SkCanvas* canvas = getDrawCanvas();
SkPaint paint;
paint.setAntiAlias(!blockAA && mParent.getAntiAliasB2DDraw());
if (mFillColor != SALCOLOR_NONE)
@@ -569,7 +630,7 @@ bool SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectTo
{
aPaint.setColor(toSkColorWithTransparency(mFillColor, fTransparency));
aPaint.setStyle(SkPaint::kFill_Style);
- mSurface->getCanvas()->drawPath(aPath, aPaint);
+ getDrawCanvas()->drawPath(aPath, aPaint);
}
if (mLineColor != SALCOLOR_NONE)
{
@@ -577,7 +638,7 @@ bool SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectTo
aPath.offset(0.5, 0.5, nullptr);
aPaint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
aPaint.setStyle(SkPaint::kStroke_Style);
- mSurface->getCanvas()->drawPath(aPath, aPaint);
+ getDrawCanvas()->drawPath(aPath, aPaint);
}
postDraw();
return true;
@@ -664,7 +725,7 @@ bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDev
// Apply the same adjustment as toSkX()/toSkY() do. Do it here even in the non-GPU
// case as it seems to produce better results.
aPath.offset(0.5, 0.5, nullptr);
- mSurface->getCanvas()->drawPath(aPath, aPaint);
+ getDrawCanvas()->drawPath(aPath, aPaint);
postDraw();
return true;
@@ -702,9 +763,8 @@ void SkiaSalGraphicsImpl::copyArea(long nDestX, long nDestY, long nSrcX, long nS
sk_sp<SkImage> image = mSurface->makeImageSnapshot();
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
- mSurface->getCanvas()->drawImageRect(
- image, SkIRect::MakeXYWH(nSrcX, nSrcY, nSrcWidth, nSrcHeight),
- SkRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight), &paint);
+ getDrawCanvas()->drawImageRect(image, SkIRect::MakeXYWH(nSrcX, nSrcY, nSrcWidth, nSrcHeight),
+ SkRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight), &paint);
postDraw();
}
@@ -725,7 +785,7 @@ void SkiaSalGraphicsImpl::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcG
sk_sp<SkImage> image = src->mSurface->makeImageSnapshot();
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
- mSurface->getCanvas()->drawImageRect(
+ getDrawCanvas()->drawImageRect(
image,
SkIRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight),
SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
@@ -880,8 +940,8 @@ void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFl
// TrackFrame is not supposed to paint outside of the polygon (usually rectangle),
// but wider stroke width usually results in that, so ensure the requirement
// by clipping.
- SkAutoCanvasRestore autoRestore(mSurface->getCanvas(), true);
- mSurface->getCanvas()->clipRect(aPath.getBounds(), SkClipOp::kIntersect, false);
+ SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
+ getDrawCanvas()->clipRect(aPath.getBounds(), SkClipOp::kIntersect, false);
SkPaint aPaint;
aPaint.setStrokeWidth(2);
float intervals[] = { 4.0f, 4.0f };
@@ -890,7 +950,7 @@ void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFl
aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
aPaint.setBlendMode(SkBlendMode::kDifference);
- mSurface->getCanvas()->drawPath(aPath, aPaint);
+ getDrawCanvas()->drawPath(aPath, aPaint);
}
else
{
@@ -938,7 +998,7 @@ void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFl
aPaint.setShader(aBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
}
- mSurface->getCanvas()->drawPath(aPath, aPaint);
+ getDrawCanvas()->drawPath(aPath, aPaint);
}
postDraw();
}
@@ -1001,7 +1061,7 @@ void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkIma
preDraw();
SAL_INFO("vcl.skia", "drawimage(" << this << "): " << rPosAry << ":" << int(eBlendMode));
- mSurface->getCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect, &aPaint);
+ getDrawCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect, &aPaint);
postDraw();
}
@@ -1018,7 +1078,7 @@ void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SkBitmap&
preDraw();
SAL_INFO("vcl.skia", "drawbitmap(" << this << "): " << rPosAry << ":" << int(eBlendMode));
- mSurface->getCanvas()->drawBitmapRect(aBitmap, aSourceRect, aDestinationRect, &aPaint);
+ getDrawCanvas()->drawBitmapRect(aBitmap, aSourceRect, aDestinationRect, &aPaint);
postDraw();
}
@@ -1067,9 +1127,9 @@ bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
SAL_INFO("vcl.skia",
"drawtransformedbitmap(" << this << "): " << rNull << ":" << rX << ":" << rY);
{
- SkAutoCanvasRestore autoRestore(mSurface->getCanvas(), true);
- mSurface->getCanvas()->concat(aMatrix);
- mSurface->getCanvas()->drawImage(tmpSurface->makeImageSnapshot(), 0, 0);
+ SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
+ getDrawCanvas()->concat(aMatrix);
+ getDrawCanvas()->drawImage(tmpSurface->makeImageSnapshot(), 0, 0);
}
postDraw();