nss: upgrade to release 3.73
[LibreOffice.git] / vcl / skia / gdiimpl.cxx
blobba9737c6544c3b3f5921d47628ceb7c99ec608f2
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <skia/gdiimpl.hxx>
22 #include <salgdi.hxx>
23 #include <skia/salbmp.hxx>
24 #include <vcl/idle.hxx>
25 #include <vcl/svapp.hxx>
26 #include <vcl/lazydelete.hxx>
27 #include <vcl/gradient.hxx>
28 #include <vcl/skia/SkiaHelper.hxx>
29 #include <skia/utils.hxx>
30 #include <skia/zone.hxx>
32 #include <SkCanvas.h>
33 #include <SkGradientShader.h>
34 #include <SkPath.h>
35 #include <SkRegion.h>
36 #include <SkDashPathEffect.h>
37 #include <GrBackendSurface.h>
38 #include <SkTextBlob.h>
39 #include <SkRSXform.h>
41 #include <numeric>
42 #include <basegfx/polygon/b2dpolygontools.hxx>
43 #include <basegfx/polygon/b2dpolypolygontools.hxx>
44 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
45 #include <o3tl/sorted_vector.hxx>
46 #include <rtl/math.hxx>
48 namespace
50 // Create Skia Path from B2DPolygon
51 // Note that polygons generally have the complication that when used
52 // for area (fill) operations they usually miss the right-most and
53 // bottom-most line of pixels of the bounding rectangle (see
54 // https://lists.freedesktop.org/archives/libreoffice/2019-November/083709.html).
55 // So be careful with rectangle->polygon conversions (generally avoid them).
56 void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath,
57 bool* hasOnlyOrthogonal = nullptr)
59 const sal_uInt32 nPointCount(rPolygon.count());
61 if (nPointCount <= 1)
62 return;
64 const bool bClosePath(rPolygon.isClosed());
65 const bool bHasCurves(rPolygon.areControlPointsUsed());
67 bool bFirst = true;
69 sal_uInt32 nCurrentIndex = 0;
70 sal_uInt32 nPreviousIndex = nPointCount - 1;
72 basegfx::B2DPoint aCurrentPoint;
73 basegfx::B2DPoint aPreviousPoint;
75 for (sal_uInt32 nIndex = 0; nIndex <= nPointCount; nIndex++)
77 if (nIndex == nPointCount && !bClosePath)
78 continue;
80 // Make sure we loop the last point to first point
81 nCurrentIndex = nIndex % nPointCount;
82 aCurrentPoint = rPolygon.getB2DPoint(nCurrentIndex);
84 if (bFirst)
86 rPath.moveTo(aCurrentPoint.getX(), aCurrentPoint.getY());
87 bFirst = false;
89 else if (!bHasCurves)
91 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY());
92 // If asked for, check whether the polygon has a line that is not
93 // strictly horizontal or vertical.
94 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
95 && aCurrentPoint.getY() != aPreviousPoint.getY())
96 *hasOnlyOrthogonal = false;
98 else
100 basegfx::B2DPoint aPreviousControlPoint = rPolygon.getNextControlPoint(nPreviousIndex);
101 basegfx::B2DPoint aCurrentControlPoint = rPolygon.getPrevControlPoint(nCurrentIndex);
103 if (aPreviousControlPoint.equal(aPreviousPoint)
104 && aCurrentControlPoint.equal(aCurrentPoint))
106 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY()); // a straight line
107 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
108 && aCurrentPoint.getY() != aPreviousPoint.getY())
109 *hasOnlyOrthogonal = false;
111 else
113 if (aPreviousControlPoint.equal(aPreviousPoint))
115 aPreviousControlPoint
116 = aPreviousPoint + ((aPreviousControlPoint - aCurrentPoint) * 0.0005);
118 if (aCurrentControlPoint.equal(aCurrentPoint))
120 aCurrentControlPoint
121 = aCurrentPoint + ((aCurrentControlPoint - aPreviousPoint) * 0.0005);
123 rPath.cubicTo(aPreviousControlPoint.getX(), aPreviousControlPoint.getY(),
124 aCurrentControlPoint.getX(), aCurrentControlPoint.getY(),
125 aCurrentPoint.getX(), aCurrentPoint.getY());
126 if (hasOnlyOrthogonal != nullptr)
127 *hasOnlyOrthogonal = false;
130 aPreviousPoint = aCurrentPoint;
131 nPreviousIndex = nCurrentIndex;
133 if (bClosePath)
135 rPath.close();
139 void addPolyPolygonToPath(const basegfx::B2DPolyPolygon& rPolyPolygon, SkPath& rPath,
140 bool* hasOnlyOrthogonal = nullptr)
142 const sal_uInt32 nPolygonCount(rPolyPolygon.count());
144 if (nPolygonCount == 0)
145 return;
147 for (const auto& rPolygon : rPolyPolygon)
149 addPolygonToPath(rPolygon, rPath, hasOnlyOrthogonal);
153 // Check if the given polygon contains a straight line. If not, it consists
154 // solely of curves.
155 bool polygonContainsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
157 if (!rPolyPolygon.areControlPointsUsed())
158 return true; // no curves at all
159 for (const auto& rPolygon : rPolyPolygon)
161 const sal_uInt32 nPointCount(rPolygon.count());
162 bool bFirst = true;
164 const bool bClosePath(rPolygon.isClosed());
166 sal_uInt32 nCurrentIndex = 0;
167 sal_uInt32 nPreviousIndex = nPointCount - 1;
169 basegfx::B2DPoint aCurrentPoint;
170 basegfx::B2DPoint aPreviousPoint;
172 for (sal_uInt32 nIndex = 0; nIndex <= nPointCount; nIndex++)
174 if (nIndex == nPointCount && !bClosePath)
175 continue;
177 // Make sure we loop the last point to first point
178 nCurrentIndex = nIndex % nPointCount;
179 if (bFirst)
180 bFirst = false;
181 else
183 basegfx::B2DPoint aPreviousControlPoint
184 = rPolygon.getNextControlPoint(nPreviousIndex);
185 basegfx::B2DPoint aCurrentControlPoint
186 = rPolygon.getPrevControlPoint(nCurrentIndex);
188 if (aPreviousControlPoint.equal(aPreviousPoint)
189 && aCurrentControlPoint.equal(aCurrentPoint))
191 return true; // found a straight line
194 aPreviousPoint = aCurrentPoint;
195 nPreviousIndex = nCurrentIndex;
198 return false; // no straight line found
201 SkColor toSkColor(Color color)
203 return SkColorSetARGB(255 - color.GetTransparency(), color.GetRed(), color.GetGreen(),
204 color.GetBlue());
207 SkColor toSkColorWithTransparency(Color aColor, double fTransparency)
209 return SkColorSetA(toSkColor(aColor), 255 * (1.0 - fTransparency));
212 SkColor toSkColorWithIntensity(Color color, int intensity)
214 return SkColorSetARGB(255 - color.GetTransparency(), color.GetRed() * intensity / 100,
215 color.GetGreen() * intensity / 100, color.GetBlue() * intensity / 100);
218 Color fromSkColor(SkColor color)
220 return Color(255 - SkColorGetA(color), SkColorGetR(color), SkColorGetG(color),
221 SkColorGetB(color));
224 // returns true if the source or destination rectangles are invalid
225 bool checkInvalidSourceOrDestination(SalTwoRect const& rPosAry)
227 return rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
228 || rPosAry.mnDestHeight <= 0;
231 } // end anonymous namespace
233 // Class that triggers flushing the backing buffer when idle.
234 class SkiaFlushIdle : public Idle
236 SkiaSalGraphicsImpl* mpGraphics;
237 #ifndef NDEBUG
238 char* debugname;
239 #endif
241 public:
242 explicit SkiaFlushIdle(SkiaSalGraphicsImpl* pGraphics)
243 : Idle(get_debug_name(pGraphics))
244 , mpGraphics(pGraphics)
246 // We don't want to be swapping before we've painted.
247 SetPriority(TaskPriority::POST_PAINT);
249 #ifndef NDEBUG
250 virtual ~SkiaFlushIdle() { free(debugname); }
251 #endif
252 const char* get_debug_name(SkiaSalGraphicsImpl* pGraphics)
254 #ifndef NDEBUG
255 // Idle keeps just a pointer, so we need to store the string
256 debugname = strdup(
257 OString("skia idle 0x" + OString::number(reinterpret_cast<sal_uIntPtr>(pGraphics), 16))
258 .getStr());
259 return debugname;
260 #else
261 (void)pGraphics;
262 return "skia idle";
263 #endif
266 virtual void Invoke() override
268 mpGraphics->performFlush();
269 Stop();
270 SetPriority(TaskPriority::HIGHEST);
274 SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvider* pProvider)
275 : mParent(rParent)
276 , mProvider(pProvider)
277 , mIsGPU(false)
278 , mLineColor(SALCOLOR_NONE)
279 , mFillColor(SALCOLOR_NONE)
280 , mXorMode(false)
281 , mFlush(new SkiaFlushIdle(this))
282 , mPendingOperationsToFlush(0)
286 SkiaSalGraphicsImpl::~SkiaSalGraphicsImpl()
288 assert(!mSurface);
289 assert(!mWindowContext);
292 void SkiaSalGraphicsImpl::Init() {}
294 void SkiaSalGraphicsImpl::createSurface()
296 SkiaZone zone;
297 if (isOffscreen())
298 createOffscreenSurface();
299 else
300 createWindowSurface();
301 mSurface->getCanvas()->save(); // see SetClipRegion()
302 mClipRegion = vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight()));
303 mDirtyRect = SkIRect::MakeWH(GetWidth(), GetHeight());
305 // We don't want to be swapping before we've painted.
306 mFlush->Stop();
307 mFlush->SetPriority(TaskPriority::POST_PAINT);
310 void SkiaSalGraphicsImpl::createWindowSurface(bool forceRaster)
312 SkiaZone zone;
313 assert(!isOffscreen());
314 assert(!mSurface);
315 assert(!mWindowContext);
316 createWindowContext(forceRaster);
317 if (mWindowContext)
318 mSurface = mWindowContext->getBackbufferSurface();
319 if (!mSurface)
321 switch (SkiaHelper::renderMethodToUse())
323 case SkiaHelper::RenderVulkan:
324 SAL_WARN("vcl.skia",
325 "cannot create Vulkan GPU window surface, falling back to Raster");
326 destroySurface(); // destroys also WindowContext
327 return createWindowSurface(true); // try again
328 case SkiaHelper::RenderRaster:
329 abort(); // This should not really happen, do not even try to cope with it.
332 mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
333 #ifdef DBG_UTIL
334 SkiaHelper::prefillSurface(mSurface);
335 #endif
338 bool SkiaSalGraphicsImpl::isOffscreen() const
340 if (mProvider == nullptr || mProvider->IsOffScreen())
341 return true;
342 // HACK: Sometimes (tdf#131939, tdf#138022, tdf#140288) VCL passes us a zero-sized window,
343 // and zero size is invalid for Skia, so force offscreen surface, where we handle this.
344 if (GetWidth() <= 0 || GetHeight() <= 0)
345 return true;
346 return false;
349 void SkiaSalGraphicsImpl::createOffscreenSurface()
351 SkiaZone zone;
352 assert(isOffscreen());
353 assert(!mSurface);
354 assert(!mWindowContext);
355 // HACK: See isOffscreen().
356 int width = std::max(1, GetWidth());
357 int height = std::max(1, GetHeight());
358 switch (SkiaHelper::renderMethodToUse())
360 case SkiaHelper::RenderVulkan:
362 if (SkiaHelper::getSharedGrDirectContext())
364 mSurface = SkiaHelper::createSkSurface(width, height);
365 if (mSurface)
367 mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
368 return;
371 break;
373 default:
374 break;
376 // Create raster surface as a fallback.
377 mSurface = SkiaHelper::createSkSurface(width, height);
378 assert(mSurface);
379 assert(!mSurface->getCanvas()->recordingContext()); // is not GPU-backed
380 mIsGPU = false;
383 void SkiaSalGraphicsImpl::destroySurface()
385 SkiaZone zone;
386 if (mSurface)
388 // check setClipRegion() invariant
389 assert(mSurface->getCanvas()->getSaveCount() == 2);
390 // if this fails, something forgot to use SkAutoCanvasRestore
391 assert(mSurface->getCanvas()->getTotalMatrix().isIdentity());
393 // If we use e.g. Vulkan, we must destroy the surface before the context,
394 // otherwise destroying the surface will reference the context. This is
395 // handled by calling destroySurface() before destroying the context.
396 // However we also need to flush the surface before destroying it,
397 // otherwise when destroying the context later there still could be queued
398 // commands referring to the surface data. This is probably a Skia bug,
399 // but work around it here.
400 if (mSurface)
401 mSurface->flushAndSubmit();
402 mSurface.reset();
403 mWindowContext.reset();
404 mIsGPU = false;
407 void SkiaSalGraphicsImpl::DeInit() { destroySurface(); }
409 void SkiaSalGraphicsImpl::preDraw()
411 assert(comphelper::SolarMutex::get()->IsCurrentThread());
412 SkiaZone::enter(); // matched in postDraw()
413 checkSurface();
414 checkPendingDrawing();
417 void SkiaSalGraphicsImpl::postDraw()
419 scheduleFlush();
420 // Skia (at least when using Vulkan) queues drawing commands and executes them only later.
421 // But tdf#136369 leads to creating and queueing many tiny bitmaps, which makes
422 // Skia slow, and may make it even run out of memory. So force a flush if such
423 // a problematic operation has been performed too many times without a flush.
424 if (mPendingOperationsToFlush > 1000)
426 mSurface->flushAndSubmit();
427 mPendingOperationsToFlush = 0;
429 SkiaZone::leave(); // matched in preDraw()
430 // If there's a problem with the GPU context, abort.
431 if (GrDirectContext* context = GrAsDirectContext(mSurface->getCanvas()->recordingContext()))
433 // Running out of memory on the GPU technically could be possibly recoverable,
434 // but we don't know the exact status of the surface (and what has or has not been drawn to it),
435 // so in practice this is unrecoverable without possible data loss.
436 if (context->oomed())
438 SAL_WARN("vcl.skia", "GPU context has run out of memory, aborting.");
439 abort();
441 // Unrecoverable problem.
442 if (context->abandoned())
444 SAL_WARN("vcl.skia", "GPU context has been abandoned, aborting.");
445 abort();
450 void SkiaSalGraphicsImpl::scheduleFlush()
452 if (!isOffscreen())
454 if (!Application::IsInExecute())
455 performFlush(); // otherwise nothing would trigger idle rendering
456 else if (!mFlush->IsActive())
457 mFlush->Start();
461 // VCL can sometimes resize us without telling us, update the surface if needed.
462 // Also create the surface on demand if it has not been created yet (it is a waste
463 // to create it in Init() if it gets recreated later anyway).
464 void SkiaSalGraphicsImpl::checkSurface()
466 if (!mSurface)
468 createSurface();
469 SAL_INFO("vcl.skia.trace",
470 "create(" << this << "): " << Size(mSurface->width(), mSurface->height()));
472 else if (GetWidth() != mSurface->width() || GetHeight() != mSurface->height())
474 if (!avoidRecreateByResize())
476 Size oldSize(mSurface->width(), mSurface->height());
477 // Recreating a surface means that the old SkSurface contents will be lost.
478 // But if a window has been resized the windowing system may send repaint events
479 // only for changed parts and VCL would not repaint the whole area, assuming
480 // that some parts have not changed (this is what seems to cause tdf#131952).
481 // So carry over the old contents for windows, even though generally everything
482 // will be usually repainted anyway.
483 sk_sp<SkImage> snapshot;
484 if (!isOffscreen())
486 flushDrawing();
487 snapshot = SkiaHelper::makeCheckedImageSnapshot(mSurface);
490 destroySurface();
491 createSurface();
493 if (snapshot)
495 SkPaint paint;
496 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
497 mSurface->getCanvas()->drawImage(snapshot, 0, 0, &paint);
499 SAL_INFO("vcl.skia.trace", "recreate(" << this << "): old " << oldSize << " new "
500 << Size(mSurface->width(), mSurface->height())
501 << " requested "
502 << Size(GetWidth(), GetHeight()));
507 bool SkiaSalGraphicsImpl::avoidRecreateByResize() const
509 // Keep the old surface if VCL sends us a broken size (see isOffscreen()).
510 if (GetWidth() == 0 || GetHeight() == 0)
511 return true;
512 return false;
515 void SkiaSalGraphicsImpl::flushDrawing()
517 if (!mSurface)
518 return;
519 checkPendingDrawing();
520 if (mXorMode)
521 applyXor();
522 mSurface->flushAndSubmit();
523 mPendingOperationsToFlush = 0;
526 bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
528 if (mClipRegion == region)
529 return true;
530 SkiaZone zone;
531 checkPendingDrawing();
532 checkSurface();
533 mClipRegion = region;
534 SAL_INFO("vcl.skia.trace", "setclipregion(" << this << "): " << region);
535 SkCanvas* canvas = mSurface->getCanvas();
536 // SkCanvas::clipRegion() can only further reduce the clip region,
537 // but we need to set the given region, which may extend it.
538 // So handle that by always having the full clip region saved on the stack
539 // and always go back to that. SkCanvas::restore() only affects the clip
540 // and the matrix.
541 assert(canvas->getSaveCount() == 2); // = there is just one save()
542 canvas->restore();
543 canvas->save();
544 setCanvasClipRegion(canvas, region);
545 return true;
548 void SkiaSalGraphicsImpl::setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region)
550 SkiaZone zone;
551 SkPath path;
552 // Always use region rectangles, regardless of what the region uses internally.
553 // That's what other VCL backends do, and trying to use addPolyPolygonToPath()
554 // in case a polygon is used leads to off-by-one errors such as tdf#133208.
555 RectangleVector rectangles;
556 region.GetRegionRectangles(rectangles);
557 for (const tools::Rectangle& rectangle : rectangles)
558 path.addRect(SkRect::MakeXYWH(rectangle.getX(), rectangle.getY(), rectangle.GetWidth(),
559 rectangle.GetHeight()));
560 path.setFillType(SkPathFillType::kEvenOdd);
561 canvas->clipPath(path);
564 void SkiaSalGraphicsImpl::ResetClipRegion()
566 setClipRegion(vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight())));
569 const vcl::Region& SkiaSalGraphicsImpl::getClipRegion() const { return mClipRegion; }
571 sal_uInt16 SkiaSalGraphicsImpl::GetBitCount() const { return 32; }
573 tools::Long SkiaSalGraphicsImpl::GetGraphicsWidth() const { return GetWidth(); }
575 void SkiaSalGraphicsImpl::SetLineColor()
577 checkPendingDrawing();
578 mLineColor = SALCOLOR_NONE;
581 void SkiaSalGraphicsImpl::SetLineColor(Color nColor)
583 checkPendingDrawing();
584 mLineColor = nColor;
587 void SkiaSalGraphicsImpl::SetFillColor()
589 checkPendingDrawing();
590 mFillColor = SALCOLOR_NONE;
593 void SkiaSalGraphicsImpl::SetFillColor(Color nColor)
595 checkPendingDrawing();
596 mFillColor = nColor;
599 void SkiaSalGraphicsImpl::SetXORMode(bool set, bool)
601 if (mXorMode == set)
602 return;
603 checkPendingDrawing();
604 SAL_INFO("vcl.skia.trace", "setxormode(" << this << "): " << set);
605 if (set)
606 mXorRegion.setEmpty();
607 else
608 applyXor();
609 mXorMode = set;
612 SkCanvas* SkiaSalGraphicsImpl::getXorCanvas()
614 SkiaZone zone;
615 assert(mXorMode);
616 // Skia does not implement xor drawing, so we need to handle it manually by redirecting
617 // to a temporary SkBitmap and then doing the xor operation on the data ourselves.
618 // There's no point in using SkSurface for GPU, we'd immediately need to get the pixels back.
619 if (!mXorCanvas)
621 // Use unpremultiplied alpha (see xor applying in applyXor()).
622 if (!mXorBitmap.tryAllocPixels(mSurface->imageInfo().makeAlphaType(kUnpremul_SkAlphaType)))
623 abort();
624 mXorBitmap.eraseARGB(0, 0, 0, 0);
625 mXorCanvas = std::make_unique<SkCanvas>(mXorBitmap);
626 setCanvasClipRegion(mXorCanvas.get(), mClipRegion);
628 return mXorCanvas.get();
631 void SkiaSalGraphicsImpl::applyXor()
633 // Apply the result from the temporary bitmap manually. This is indeed
634 // slow, but it doesn't seem to be needed often and is optimized
635 // in each operation by extending mXorRegion with the area that should be
636 // updated.
637 assert(mXorMode);
638 if (!mSurface || !mXorCanvas
639 || !mXorRegion.op(SkIRect::MakeXYWH(0, 0, mSurface->width(), mSurface->height()),
640 SkRegion::kIntersect_Op))
642 mXorRegion.setEmpty();
643 return;
645 SAL_INFO("vcl.skia.trace", "applyxor(" << this << "): " << mXorRegion);
646 // Copy the surface contents to another pixmap.
647 SkBitmap surfaceBitmap;
648 // Use unpremultiplied alpha format, so that we do not have to do the conversions to get
649 // the RGB and back (Skia will do it when converting, but it'll be presumably faster at it).
650 if (!surfaceBitmap.tryAllocPixels(mSurface->imageInfo().makeAlphaType(kUnpremul_SkAlphaType)))
651 abort();
652 SkPaint paint;
653 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
654 SkCanvas canvas(surfaceBitmap);
655 canvas.drawImageRect(SkiaHelper::makeCheckedImageSnapshot(mSurface), mXorRegion.getBounds(),
656 SkRect::Make(mXorRegion.getBounds()), &paint);
657 // xor to surfaceBitmap
658 assert(surfaceBitmap.info().alphaType() == kUnpremul_SkAlphaType);
659 assert(mXorBitmap.info().alphaType() == kUnpremul_SkAlphaType);
660 assert(surfaceBitmap.bytesPerPixel() == 4);
661 assert(mXorBitmap.bytesPerPixel() == 4);
662 for (SkRegion::Iterator it(mXorRegion); !it.done(); it.next())
664 for (int y = it.rect().top(); y < it.rect().bottom(); ++y)
666 uint8_t* data = static_cast<uint8_t*>(surfaceBitmap.getAddr(it.rect().x(), y));
667 const uint8_t* xordata = static_cast<uint8_t*>(mXorBitmap.getAddr(it.rect().x(), y));
668 for (int x = 0; x < it.rect().width(); ++x)
670 *data++ ^= *xordata++;
671 *data++ ^= *xordata++;
672 *data++ ^= *xordata++;
673 // alpha is not xor-ed
674 data++;
675 xordata++;
679 surfaceBitmap.notifyPixelsChanged();
680 mSurface->getCanvas()->drawBitmapRect(surfaceBitmap, mXorRegion.getBounds(),
681 SkRect::Make(mXorRegion.getBounds()), &paint);
682 mXorCanvas.reset();
683 mXorBitmap.reset();
684 mXorRegion.setEmpty();
687 void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor)
689 checkPendingDrawing();
690 switch (nROPColor)
692 case SalROPColor::N0:
693 mLineColor = Color(0, 0, 0);
694 break;
695 case SalROPColor::N1:
696 mLineColor = Color(0xff, 0xff, 0xff);
697 break;
698 case SalROPColor::Invert:
699 mLineColor = Color(0xff, 0xff, 0xff);
700 break;
704 void SkiaSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor)
706 checkPendingDrawing();
707 switch (nROPColor)
709 case SalROPColor::N0:
710 mFillColor = Color(0, 0, 0);
711 break;
712 case SalROPColor::N1:
713 mFillColor = Color(0xff, 0xff, 0xff);
714 break;
715 case SalROPColor::Invert:
716 mFillColor = Color(0xff, 0xff, 0xff);
717 break;
721 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY)
723 drawPixel(nX, nY, mLineColor);
726 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
728 if (nColor == SALCOLOR_NONE)
729 return;
730 preDraw();
731 SAL_INFO("vcl.skia.trace", "drawpixel(" << this << "): " << Point(nX, nY) << ":" << nColor);
732 addUpdateRegion(SkRect::MakeXYWH(nX, nY, 1, 1));
733 SkPaint paint;
734 paint.setColor(toSkColor(nColor));
735 // Apparently drawPixel() is actually expected to set the pixel and not draw it.
736 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
737 getDrawCanvas()->drawPoint(toSkX(nX), toSkY(nY), paint);
738 postDraw();
741 void SkiaSalGraphicsImpl::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
742 tools::Long nY2)
744 if (mLineColor == SALCOLOR_NONE)
745 return;
746 preDraw();
747 SAL_INFO("vcl.skia.trace", "drawline(" << this << "): " << Point(nX1, nY1) << "->"
748 << Point(nX2, nY2) << ":" << mLineColor);
749 addUpdateRegion(SkRect::MakeLTRB(nX1, nY1, nX2, nY2).makeSorted());
750 SkPaint paint;
751 paint.setColor(toSkColor(mLineColor));
752 paint.setAntiAlias(mParent.getAntiAlias());
753 getDrawCanvas()->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
754 postDraw();
757 void SkiaSalGraphicsImpl::privateDrawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
758 tools::Long nHeight, double fTransparency,
759 bool blockAA)
761 preDraw();
762 SAL_INFO("vcl.skia.trace",
763 "privatedrawrect(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight)
764 << ":" << mLineColor << ":" << mFillColor << ":" << fTransparency);
765 addUpdateRegion(SkRect::MakeXYWH(nX, nY, nWidth, nHeight));
766 SkCanvas* canvas = getDrawCanvas();
767 SkPaint paint;
768 paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
769 if (mFillColor != SALCOLOR_NONE)
771 paint.setColor(toSkColorWithTransparency(mFillColor, fTransparency));
772 paint.setStyle(SkPaint::kFill_Style);
773 // HACK: If the polygon is just a line, it still should be drawn. But when filling
774 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
775 if (mLineColor == SALCOLOR_NONE && SkSize::Make(nWidth, nHeight).isEmpty())
776 paint.setStyle(SkPaint::kStroke_Style);
777 canvas->drawIRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), paint);
779 if (mLineColor != SALCOLOR_NONE)
781 paint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
782 paint.setStyle(SkPaint::kStroke_Style);
783 // The obnoxious "-1 DrawRect()" hack that I don't understand the purpose of (and I'm not sure
784 // if anybody does), but without it some cases do not work. The max() is needed because Skia
785 // will not draw anything if width or height is 0.
786 canvas->drawIRect(SkIRect::MakeXYWH(nX, nY, std::max(tools::Long(1), nWidth - 1),
787 std::max(tools::Long(1), nHeight - 1)),
788 paint);
790 postDraw();
793 void SkiaSalGraphicsImpl::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
794 tools::Long nHeight)
796 privateDrawAlphaRect(nX, nY, nWidth, nHeight, 0.0, true);
799 void SkiaSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
801 basegfx::B2DPolygon aPolygon;
802 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
803 for (sal_uInt32 i = 1; i < nPoints; ++i)
804 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
805 aPolygon.setClosed(false);
807 drawPolyLine(basegfx::B2DHomMatrix(), aPolygon, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
808 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false);
811 void SkiaSalGraphicsImpl::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
813 basegfx::B2DPolygon aPolygon;
814 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
815 for (sal_uInt32 i = 1; i < nPoints; ++i)
816 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
818 drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPolygon), 0.0);
821 void SkiaSalGraphicsImpl::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
822 const Point** pPtAry)
824 basegfx::B2DPolyPolygon aPolyPolygon;
825 for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
827 sal_uInt32 nPoints = pPoints[nPolygon];
828 if (nPoints)
830 const Point* pSubPoints = pPtAry[nPolygon];
831 basegfx::B2DPolygon aPolygon;
832 aPolygon.append(basegfx::B2DPoint(pSubPoints->getX(), pSubPoints->getY()), nPoints);
833 for (sal_uInt32 i = 1; i < nPoints; ++i)
834 aPolygon.setB2DPoint(i,
835 basegfx::B2DPoint(pSubPoints[i].getX(), pSubPoints[i].getY()));
837 aPolyPolygon.append(aPolygon);
841 drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon, 0.0);
844 bool SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
845 const basegfx::B2DPolyPolygon& rPolyPolygon,
846 double fTransparency)
848 const bool bHasFill(mFillColor != SALCOLOR_NONE);
849 const bool bHasLine(mLineColor != SALCOLOR_NONE);
851 if (rPolyPolygon.count() == 0 || !(bHasFill || bHasLine) || fTransparency < 0.0
852 || fTransparency >= 1.0)
853 return true;
855 basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
856 aPolyPolygon.transform(rObjectToDevice);
858 SAL_INFO("vcl.skia.trace", "drawpolypolygon(" << this << "): " << aPolyPolygon << ":"
859 << mLineColor << ":" << mFillColor);
861 if (delayDrawPolyPolygon(aPolyPolygon, fTransparency))
863 scheduleFlush();
864 return true;
867 performDrawPolyPolygon(aPolyPolygon, fTransparency, mParent.getAntiAlias());
868 return true;
871 void SkiaSalGraphicsImpl::performDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
872 double fTransparency, bool useAA)
874 preDraw();
876 SkPath polygonPath;
877 bool hasOnlyOrthogonal = true;
878 addPolyPolygonToPath(aPolyPolygon, polygonPath, &hasOnlyOrthogonal);
879 polygonPath.setFillType(SkPathFillType::kEvenOdd);
880 addUpdateRegion(polygonPath.getBounds());
882 SkPaint aPaint;
883 aPaint.setAntiAlias(useAA);
885 // For lines we use toSkX()/toSkY() in order to pass centers of pixels to Skia,
886 // as that leads to better results with floating-point coordinates
887 // (e.g. https://bugs.chromium.org/p/skia/issues/detail?id=9611).
888 // But that means that we generally need to use it also for areas, so that they
889 // line up properly if used together (tdf#134346).
890 // On the other hand, with AA enabled and rectangular areas, this leads to fuzzy
891 // edges (tdf#137329). But since rectangular areas line up perfectly to pixels
892 // everywhere, it shouldn't be necessary to do this for them.
893 // So if AA is enabled, avoid this fixup for rectangular areas.
894 if (!useAA || !hasOnlyOrthogonal)
896 // We normally use pixel at their center positions, but slightly off (see toSkX/Y()).
897 // With AA lines that "slightly off" causes tiny changes of color, making some tests
898 // fail. Since moving AA-ed line slightly to a side doesn't cause any real visual
899 // difference, just place exactly at the center. tdf#134346
900 const SkScalar posFix = useAA ? toSkXYFix : 0;
901 polygonPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
903 if (mFillColor != SALCOLOR_NONE)
905 aPaint.setColor(toSkColorWithTransparency(mFillColor, fTransparency));
906 aPaint.setStyle(SkPaint::kFill_Style);
907 // HACK: If the polygon is just a line, it still should be drawn. But when filling
908 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
909 if (mLineColor == SALCOLOR_NONE && polygonPath.getBounds().isEmpty())
910 aPaint.setStyle(SkPaint::kStroke_Style);
911 getDrawCanvas()->drawPath(polygonPath, aPaint);
913 if (mLineColor != SALCOLOR_NONE)
915 aPaint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
916 aPaint.setStyle(SkPaint::kStroke_Style);
917 getDrawCanvas()->drawPath(polygonPath, aPaint);
919 postDraw();
920 #if defined LINUX
921 // WORKAROUND: The logo in the about dialog has drawing errors. This seems to happen
922 // only on Linux (not Windows on the same machine), with both AMDGPU and Mesa,
923 // and only when antialiasing is enabled. Flushing seems to avoid the problem.
924 if (useAA && SkiaHelper::getVendor() == DriverBlocklist::VendorAMD)
925 mSurface->flushAndSubmit();
926 #endif
929 namespace
931 struct LessThan
933 bool operator()(const basegfx::B2DPoint& point1, const basegfx::B2DPoint& point2) const
935 if (basegfx::fTools::equal(point1.getX(), point2.getX()))
936 return basegfx::fTools::less(point1.getY(), point2.getY());
937 return basegfx::fTools::less(point1.getX(), point2.getX());
940 } // namespace
942 bool SkiaSalGraphicsImpl::delayDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
943 double fTransparency)
945 // There is some code that needlessly subdivides areas into adjacent rectangles,
946 // but Skia doesn't line them up perfectly if AA is enabled (e.g. Cairo, Qt5 do,
947 // but Skia devs claim it's working as intended
948 // https://groups.google.com/d/msg/skia-discuss/NlKpD2X_5uc/Vuwd-kyYBwAJ).
949 // An example is tdf#133016, which triggers SvgStyleAttributes::add_stroke()
950 // implementing a line stroke as a bunch of polygons instead of just one, and
951 // SvgLinearAtomPrimitive2D::create2DDecomposition() creates a gradient
952 // as a series of polygons of gradually changing color. Those places should be
953 // changed, but try to merge those split polygons back into the original one,
954 // where the needlessly created edges causing problems will not exist.
955 // This means drawing of such polygons needs to be delayed, so that they can
956 // be possibly merged with the next one.
957 // Merge only polygons of the same properties (color, etc.), so the gradient problem
958 // actually isn't handled here.
960 // Only AA polygons need merging, because they do not line up well because of the AA of the edges.
961 if (!mParent.getAntiAlias())
962 return false;
963 // Only filled polygons without an outline are problematic.
964 if (mFillColor == SALCOLOR_NONE || mLineColor != SALCOLOR_NONE)
965 return false;
966 // Merge only simple polygons, real polypolygons most likely aren't needlessly split,
967 // so they do not need joining.
968 if (aPolyPolygon.count() != 1)
969 return false;
970 // If the polygon is not closed, it doesn't mark an area to be filled.
971 if (!aPolyPolygon.isClosed())
972 return false;
973 // If a polygon does not contain a straight line, i.e. it's all curves, then do not merge.
974 // First of all that's even more expensive, and second it's very unlikely that it's a polygon
975 // split into more polygons.
976 if (!polygonContainsLine(aPolyPolygon))
977 return false;
979 if (mLastPolyPolygonInfo.polygons.size() != 0
980 && (mLastPolyPolygonInfo.transparency != fTransparency
981 || !mLastPolyPolygonInfo.bounds.overlaps(aPolyPolygon.getB2DRange())))
983 checkPendingDrawing(); // Cannot be parts of the same larger polygon, draw the last and reset.
985 if (!mLastPolyPolygonInfo.polygons.empty())
987 assert(aPolyPolygon.count() == 1);
988 assert(mLastPolyPolygonInfo.polygons.back().count() == 1);
989 // Check if the new and the previous polygon share at least one point. If not, then they
990 // cannot be adjacent polygons, so there's no point in trying to merge them.
991 bool sharePoint = false;
992 const basegfx::B2DPolygon& poly1 = aPolyPolygon.getB2DPolygon(0);
993 const basegfx::B2DPolygon& poly2 = mLastPolyPolygonInfo.polygons.back().getB2DPolygon(0);
994 o3tl::sorted_vector<basegfx::B2DPoint, LessThan> poly1Points; // for O(n log n)
995 poly1Points.reserve(poly1.count());
996 for (sal_uInt32 i = 0; i < poly1.count(); ++i)
997 poly1Points.insert(poly1.getB2DPoint(i));
998 for (sal_uInt32 i = 0; i < poly2.count(); ++i)
999 if (poly1Points.find(poly2.getB2DPoint(i)) != poly1Points.end())
1001 sharePoint = true;
1002 break;
1004 if (!sharePoint)
1005 checkPendingDrawing(); // Draw the previous one and reset.
1007 // Collect the polygons that can be possibly merged. Do the merging only once at the end,
1008 // because it's not a cheap operation.
1009 mLastPolyPolygonInfo.polygons.push_back(aPolyPolygon);
1010 mLastPolyPolygonInfo.bounds.expand(aPolyPolygon.getB2DRange());
1011 mLastPolyPolygonInfo.transparency = fTransparency;
1012 return true;
1015 // Tdf#140848 - basegfx::utils::mergeToSinglePolyPolygon() seems to have rounding
1016 // errors that sometimes cause it to merge incorrectly.
1017 static void roundPolygonPoints(basegfx::B2DPolyPolygon& polyPolygon)
1019 for (basegfx::B2DPolygon& polygon : polyPolygon)
1021 polygon.makeUnique();
1022 for (sal_uInt32 i = 0; i < polygon.count(); ++i)
1023 polygon.setB2DPoint(i, basegfx::B2DPoint(basegfx::fround(polygon.getB2DPoint(i))));
1024 // Control points are saved as vectors relative to points, so hopefully
1025 // there's no need to round those.
1029 void SkiaSalGraphicsImpl::checkPendingDrawing()
1031 if (mLastPolyPolygonInfo.polygons.size() != 0)
1032 { // Flush any pending polygon drawing.
1033 basegfx::B2DPolyPolygonVector polygons;
1034 std::swap(polygons, mLastPolyPolygonInfo.polygons);
1035 double transparency = mLastPolyPolygonInfo.transparency;
1036 mLastPolyPolygonInfo.bounds.reset();
1037 if (polygons.size() == 1)
1038 performDrawPolyPolygon(polygons.front(), transparency, true);
1039 else
1041 for (basegfx::B2DPolyPolygon& p : polygons)
1042 roundPolygonPoints(p);
1043 performDrawPolyPolygon(basegfx::utils::mergeToSinglePolyPolygon(polygons), transparency,
1044 true);
1049 bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
1050 const basegfx::B2DPolygon& rPolyLine, double fTransparency,
1051 double fLineWidth, const std::vector<double>* pStroke,
1052 basegfx::B2DLineJoin eLineJoin,
1053 css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
1054 bool bPixelSnapHairline)
1056 if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0
1057 || mLineColor == SALCOLOR_NONE)
1059 return true;
1062 preDraw();
1063 SAL_INFO("vcl.skia.trace", "drawpolyline(" << this << "): " << rPolyLine << ":" << mLineColor);
1065 // tdf#124848 get correct LineWidth in discrete coordinates,
1066 if (fLineWidth == 0) // hairline
1067 fLineWidth = 1.0;
1068 else // Adjust line width for object-to-device scale.
1069 fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
1071 // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
1072 basegfx::B2DPolyPolygon aPolyPolygonLine;
1073 aPolyPolygonLine.append(rPolyLine);
1074 aPolyPolygonLine.transform(rObjectToDevice);
1075 if (bPixelSnapHairline)
1077 aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine);
1080 // Setup Line Join
1081 SkPaint::Join eSkLineJoin = SkPaint::kMiter_Join;
1082 switch (eLineJoin)
1084 case basegfx::B2DLineJoin::Bevel:
1085 eSkLineJoin = SkPaint::kBevel_Join;
1086 break;
1087 case basegfx::B2DLineJoin::Round:
1088 eSkLineJoin = SkPaint::kRound_Join;
1089 break;
1090 case basegfx::B2DLineJoin::NONE:
1091 case basegfx::B2DLineJoin::Miter:
1092 eSkLineJoin = SkPaint::kMiter_Join;
1093 break;
1096 // convert miter minimum angle to miter limit
1097 double fMiterLimit = 1.0 / std::sin(fMiterMinimumAngle / 2.0);
1099 // Setup Line Cap
1100 SkPaint::Cap eSkLineCap(SkPaint::kButt_Cap);
1102 switch (eLineCap)
1104 case css::drawing::LineCap_ROUND:
1105 eSkLineCap = SkPaint::kRound_Cap;
1106 break;
1107 case css::drawing::LineCap_SQUARE:
1108 eSkLineCap = SkPaint::kSquare_Cap;
1109 break;
1110 default: // css::drawing::LineCap_BUTT:
1111 eSkLineCap = SkPaint::kButt_Cap;
1112 break;
1115 SkPaint aPaint;
1116 aPaint.setStyle(SkPaint::kStroke_Style);
1117 aPaint.setStrokeCap(eSkLineCap);
1118 aPaint.setStrokeJoin(eSkLineJoin);
1119 aPaint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
1120 aPaint.setStrokeMiter(fMiterLimit);
1121 aPaint.setStrokeWidth(fLineWidth);
1122 aPaint.setAntiAlias(mParent.getAntiAlias());
1123 // See the tdf#134346 comment above.
1124 const SkScalar posFix = mParent.getAntiAlias() ? toSkXYFix : 0;
1126 if (pStroke && std::accumulate(pStroke->begin(), pStroke->end(), 0.0) != 0)
1128 std::vector<SkScalar> intervals;
1129 // Transform size by the matrix.
1130 for (double stroke : *pStroke)
1131 intervals.push_back((rObjectToDevice * basegfx::B2DVector(stroke, 0)).getLength());
1132 aPaint.setPathEffect(SkDashPathEffect::Make(intervals.data(), intervals.size(), 0));
1135 // Skia does not support basegfx::B2DLineJoin::NONE, so in that case batch only if lines
1136 // are not wider than a pixel.
1137 if (eLineJoin != basegfx::B2DLineJoin::NONE || fLineWidth <= 1.0)
1139 SkPath aPath;
1140 aPath.setFillType(SkPathFillType::kEvenOdd);
1141 for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
1142 addPolygonToPath(aPolyPolygonLine.getB2DPolygon(a), aPath);
1143 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1144 addUpdateRegion(aPath.getBounds());
1145 getDrawCanvas()->drawPath(aPath, aPaint);
1147 else
1149 for (sal_uInt32 i = 0; i < aPolyPolygonLine.count(); ++i)
1151 const basegfx::B2DPolygon& rPolygon = aPolyPolygonLine.getB2DPolygon(i);
1152 sal_uInt32 nPoints = rPolygon.count();
1153 bool bClosed = rPolygon.isClosed();
1154 for (sal_uInt32 j = 0; j < (bClosed ? nPoints : nPoints - 1); ++j)
1156 sal_uInt32 index1 = (j + 0) % nPoints;
1157 sal_uInt32 index2 = (j + 1) % nPoints;
1158 SkPath aPath;
1159 aPath.moveTo(rPolygon.getB2DPoint(index1).getX(),
1160 rPolygon.getB2DPoint(index1).getY());
1161 aPath.lineTo(rPolygon.getB2DPoint(index2).getX(),
1162 rPolygon.getB2DPoint(index2).getY());
1164 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1165 addUpdateRegion(aPath.getBounds());
1166 getDrawCanvas()->drawPath(aPath, aPaint);
1171 postDraw();
1173 return true;
1176 bool SkiaSalGraphicsImpl::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*)
1178 return false;
1181 bool SkiaSalGraphicsImpl::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*)
1183 return false;
1186 bool SkiaSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*, const Point* const*,
1187 const PolyFlags* const*)
1189 return false;
1192 static void copyArea(SkCanvas* canvas, sk_sp<SkSurface> surface, tools::Long nDestX,
1193 tools::Long nDestY, tools::Long nSrcX, tools::Long nSrcY,
1194 tools::Long nSrcWidth, tools::Long nSrcHeight, bool srcIsRaster,
1195 bool destIsRaster)
1197 // Using SkSurface::draw() should be more efficient than SkSurface::makeImageSnapshot(),
1198 // because it may detect copying to itself and avoid some needless copies.
1199 // But it has problems with drawing to itself
1200 // (https://groups.google.com/forum/#!topic/skia-discuss/6yiuw24jv0I) and also
1201 // raster surfaces do not avoid a copy of the source
1202 // (https://groups.google.com/forum/#!topic/skia-discuss/S3FMpCi82k0).
1203 // Finally, there's not much point if one of them is raster and the other is not (chrome/m86 even crashes).
1204 if (canvas == surface->getCanvas() || srcIsRaster || (srcIsRaster != destIsRaster))
1206 SkPaint paint;
1207 paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
1208 canvas->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(surface),
1209 SkIRect::MakeXYWH(nSrcX, nSrcY, nSrcWidth, nSrcHeight),
1210 SkRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight), &paint);
1211 return;
1213 // SkCanvas::draw() cannot do a subrectangle, so clip.
1214 canvas->save();
1215 canvas->clipRect(SkRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
1216 SkPaint paint;
1217 paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
1218 surface->draw(canvas, nDestX - nSrcX, nDestY - nSrcY, &paint);
1219 canvas->restore();
1222 void SkiaSalGraphicsImpl::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
1223 tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
1224 bool /*bWindowInvalidate*/)
1226 if (nDestX == nSrcX && nDestY == nSrcY)
1227 return;
1228 preDraw();
1229 SAL_INFO("vcl.skia.trace", "copyarea("
1230 << this << "): " << Point(nSrcX, nSrcY) << "->"
1231 << SkIRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
1232 assert(!mXorMode);
1233 addUpdateRegion(SkRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
1234 ::copyArea(getDrawCanvas(), mSurface, nDestX, nDestY, nSrcX, nSrcY, nSrcWidth, nSrcHeight,
1235 !isGPU(), !isGPU());
1236 postDraw();
1239 void SkiaSalGraphicsImpl::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
1241 preDraw();
1242 SkiaSalGraphicsImpl* src;
1243 if (pSrcGraphics)
1245 assert(dynamic_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl()));
1246 src = static_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl());
1247 src->checkSurface();
1248 src->flushDrawing();
1250 else
1252 src = this;
1253 assert(!mXorMode);
1255 assert(!mXorMode);
1256 addUpdateRegion(SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1257 rPosAry.mnDestHeight));
1258 if (rPosAry.mnSrcWidth == rPosAry.mnDestWidth && rPosAry.mnSrcHeight == rPosAry.mnDestHeight)
1260 auto srcDebug = [&]() -> std::string {
1261 if (src == this)
1262 return "(self)";
1263 else
1265 std::ostringstream stream;
1266 stream << "(" << src << ")";
1267 return stream.str();
1270 SAL_INFO("vcl.skia.trace",
1271 "copybits(" << this << "): " << srcDebug() << " copy area: " << rPosAry);
1272 ::copyArea(getDrawCanvas(), src->mSurface, rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX,
1273 rPosAry.mnSrcY, rPosAry.mnDestWidth, rPosAry.mnDestHeight, !src->isGPU(),
1274 !isGPU());
1276 else
1278 SAL_INFO("vcl.skia.trace", "copybits(" << this << "): (" << src << "): " << rPosAry);
1279 // Do not use makeImageSnapshot(rect), as that one may make a needless data copy.
1280 sk_sp<SkImage> image = SkiaHelper::makeCheckedImageSnapshot(src->mSurface);
1281 SkPaint paint;
1282 paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
1283 if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth
1284 || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1285 paint.setFilterQuality(kHigh_SkFilterQuality);
1286 getDrawCanvas()->drawImageRect(image,
1287 SkIRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY,
1288 rPosAry.mnSrcWidth, rPosAry.mnSrcHeight),
1289 SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY,
1290 rPosAry.mnDestWidth, rPosAry.mnDestHeight),
1291 &paint);
1293 postDraw();
1296 bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect& rPosAry, const SalBitmap& rBitmap)
1298 if (checkInvalidSourceOrDestination(rPosAry))
1299 return false;
1301 assert(dynamic_cast<const SkiaSalBitmap*>(&rBitmap));
1302 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rBitmap);
1303 // This is used by VirtualDevice in the alpha mode for the "alpha" layer which
1304 // is actually one-minus-alpha (opacity). Therefore white=0xff=transparent,
1305 // black=0x00=opaque. So the result is transparent only if both the inputs
1306 // are transparent. Since for blending operations white=1.0 and black=0.0,
1307 // kMultiply should handle exactly that (transparent*transparent=transparent,
1308 // opaque*transparent=opaque). And guessing from the "floor" in TYPE_BLEND in opengl's
1309 // combinedTextureFragmentShader.glsl, the layer is not even alpha values but
1310 // simply yes-or-no mask.
1311 // See also blendAlphaBitmap().
1312 if (rSkiaBitmap.IsFullyOpaqueAsAlpha())
1314 // Optimization. If the bitmap means fully opaque, it's all zero's. In CPU
1315 // mode it should be faster to just copy instead of SkBlendMode::kMultiply.
1316 drawBitmap(rPosAry, rSkiaBitmap);
1318 else
1319 drawBitmap(rPosAry, rSkiaBitmap, SkBlendMode::kMultiply);
1320 return true;
1323 bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry,
1324 const SalBitmap& rSourceBitmap,
1325 const SalBitmap& rMaskBitmap,
1326 const SalBitmap& rAlphaBitmap)
1328 if (checkInvalidSourceOrDestination(rPosAry))
1329 return false;
1331 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1332 assert(dynamic_cast<const SkiaSalBitmap*>(&rMaskBitmap));
1333 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1334 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1335 const SkiaSalBitmap& rSkiaMaskBitmap = static_cast<const SkiaSalBitmap&>(rMaskBitmap);
1336 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1338 if (rSkiaMaskBitmap.IsFullyOpaqueAsAlpha())
1340 // Optimization. If the mask of the bitmap to be blended means it's actually opaque,
1341 // just draw the bitmap directly (that's what the math below will result in).
1342 drawBitmap(rPosAry, rSkiaSourceBitmap);
1343 return true;
1345 // This was originally implemented for the OpenGL drawing method and it is poorly documented.
1346 // The source and mask bitmaps are the usual data and alpha bitmaps, and 'alpha'
1347 // is the "alpha" layer of the VirtualDevice (the alpha in VirtualDevice is also stored
1348 // as a separate bitmap). Now if I understand it correctly these two alpha masks first need
1349 // to be combined into the actual alpha mask to be used. The formula for TYPE_BLEND
1350 // in opengl's combinedTextureFragmentShader.glsl is
1351 // "result_alpha = 1.0 - (1.0 - floor(alpha)) * mask".
1352 // See also blendBitmap().
1354 // First do the "( 1 - alpha ) * mask"
1355 // (no idea how to do "floor", but hopefully not needed in practice).
1356 sk_sp<SkShader> shaderAlpha
1357 = SkShaders::Blend(SkBlendMode::kDstOut, rSkiaMaskBitmap.GetAlphaSkShader(),
1358 rSkiaAlphaBitmap.GetAlphaSkShader());
1359 // And now draw the bitmap with "1 - x", where x is the "( 1 - alpha ) * mask".
1360 sk_sp<SkShader> shader
1361 = SkShaders::Blend(SkBlendMode::kSrcOut, shaderAlpha, rSkiaSourceBitmap.GetSkShader());
1362 drawShader(rPosAry, shader);
1363 return true;
1366 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
1368 if (checkInvalidSourceOrDestination(rPosAry))
1369 return;
1371 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1372 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1374 drawBitmap(rPosAry, rSkiaSourceBitmap);
1377 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1378 const SalBitmap& rMaskBitmap)
1380 drawAlphaBitmap(rPosAry, rSalBitmap, rMaskBitmap);
1383 void SkiaSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1384 Color nMaskColor)
1386 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1387 const SkiaSalBitmap& skiaBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1388 drawShader(rPosAry,
1389 SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1390 SkShaders::Color(toSkColor(nMaskColor)),
1391 skiaBitmap.GetAlphaSkShader()));
1394 std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(tools::Long nX, tools::Long nY,
1395 tools::Long nWidth, tools::Long nHeight)
1397 SkiaZone zone;
1398 checkSurface();
1399 SAL_INFO("vcl.skia.trace",
1400 "getbitmap(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight));
1401 flushDrawing();
1402 // TODO makeImageSnapshot(rect) may copy the data, which may be a waste if this is used
1403 // e.g. for VirtualDevice's lame alpha blending, in which case the image will eventually end up
1404 // in blendAlphaBitmap(), where we could simply use the proper rect of the image.
1405 sk_sp<SkImage> image = SkiaHelper::makeCheckedImageSnapshot(
1406 mSurface, SkIRect::MakeXYWH(nX, nY, nWidth, nHeight));
1407 return std::make_shared<SkiaSalBitmap>(image);
1410 Color SkiaSalGraphicsImpl::getPixel(tools::Long nX, tools::Long nY)
1412 SkiaZone zone;
1413 checkSurface();
1414 SAL_INFO("vcl.skia.trace", "getpixel(" << this << "): " << Point(nX, nY));
1415 flushDrawing();
1416 // This is presumably slow, but getPixel() should be generally used only by unit tests.
1417 SkBitmap bitmap;
1418 if (!bitmap.tryAllocN32Pixels(GetWidth(), GetHeight()))
1419 abort();
1420 if (!mSurface->readPixels(bitmap, 0, 0))
1421 abort();
1422 return fromSkColor(bitmap.getColor(nX, nY));
1425 void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFlags)
1427 preDraw();
1428 SAL_INFO("vcl.skia.trace", "invert(" << this << "): " << rPoly << ":" << int(eFlags));
1429 assert(!mXorMode);
1430 // Intel Vulkan drivers (up to current 0.401.3889) have a problem
1431 // with SkBlendMode::kDifference(?) and surfaces wider than 1024 pixels, resulting
1432 // in drawing errors. Work that around by fetching the relevant part of the surface
1433 // and drawing using CPU.
1434 bool intelHack
1435 = (isGPU() && SkiaHelper::getVendor() == DriverBlocklist::VendorIntel && !mXorMode);
1436 SkPath aPath;
1437 addPolygonToPath(rPoly, aPath);
1438 aPath.setFillType(SkPathFillType::kEvenOdd);
1439 addUpdateRegion(aPath.getBounds());
1440 // TrackFrame just inverts a dashed path around the polygon
1441 if (eFlags == SalInvert::TrackFrame)
1443 // TrackFrame is not supposed to paint outside of the polygon (usually rectangle),
1444 // but wider stroke width usually results in that, so ensure the requirement
1445 // by clipping.
1446 SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
1447 getDrawCanvas()->clipRect(aPath.getBounds(), SkClipOp::kIntersect, false);
1448 SkPaint aPaint;
1449 aPaint.setStrokeWidth(2);
1450 float intervals[] = { 4.0f, 4.0f };
1451 aPaint.setStyle(SkPaint::kStroke_Style);
1452 aPaint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
1453 aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
1454 aPaint.setBlendMode(SkBlendMode::kDifference);
1455 if (!intelHack)
1456 getDrawCanvas()->drawPath(aPath, aPaint);
1457 else
1459 SkRect area;
1460 aPath.getBounds().roundOut(&area);
1461 SkRect size = SkRect::MakeWH(area.width(), area.height());
1462 sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(area.width(), area.height(),
1463 SkiaHelper::surfaceProps());
1464 SkPaint copy;
1465 copy.setBlendMode(SkBlendMode::kSrc);
1466 flushDrawing();
1467 surface->getCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(mSurface),
1468 area, size, &copy);
1469 aPath.offset(-area.x(), -area.y());
1470 surface->getCanvas()->drawPath(aPath, aPaint);
1471 getDrawCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(surface), size,
1472 area, &copy);
1475 else
1477 SkPaint aPaint;
1478 aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
1479 aPaint.setStyle(SkPaint::kFill_Style);
1480 aPaint.setBlendMode(SkBlendMode::kDifference);
1482 // N50 inverts in checker pattern
1483 if (eFlags == SalInvert::N50)
1485 // This creates 2x2 checker pattern bitmap
1486 // TODO Use SkiaHelper::createSkSurface() and cache the image
1487 SkBitmap aBitmap;
1488 aBitmap.allocN32Pixels(2, 2);
1489 const SkPMColor white = SkPreMultiplyARGB(0xFF, 0xFF, 0xFF, 0xFF);
1490 const SkPMColor black = SkPreMultiplyARGB(0xFF, 0x00, 0x00, 0x00);
1491 SkPMColor* scanline;
1492 scanline = aBitmap.getAddr32(0, 0);
1493 *scanline++ = white;
1494 *scanline++ = black;
1495 scanline = aBitmap.getAddr32(0, 1);
1496 *scanline++ = black;
1497 *scanline++ = white;
1498 aBitmap.setImmutable();
1499 // The bitmap is repeated in both directions the checker pattern is as big
1500 // as the polygon (usually rectangle)
1501 aPaint.setShader(aBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
1503 if (!intelHack)
1504 getDrawCanvas()->drawPath(aPath, aPaint);
1505 else
1507 SkRect area;
1508 aPath.getBounds().roundOut(&area);
1509 SkRect size = SkRect::MakeWH(area.width(), area.height());
1510 sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(area.width(), area.height(),
1511 SkiaHelper::surfaceProps());
1512 SkPaint copy;
1513 copy.setBlendMode(SkBlendMode::kSrc);
1514 flushDrawing();
1515 surface->getCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(mSurface),
1516 area, size, &copy);
1517 aPath.offset(-area.x(), -area.y());
1518 surface->getCanvas()->drawPath(aPath, aPaint);
1519 getDrawCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(surface), size,
1520 area, &copy);
1523 postDraw();
1526 void SkiaSalGraphicsImpl::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
1527 tools::Long nHeight, SalInvert eFlags)
1529 basegfx::B2DRectangle aRectangle(nX, nY, nX + nWidth, nY + nHeight);
1530 auto aRect = basegfx::utils::createPolygonFromRect(aRectangle);
1531 invert(aRect, eFlags);
1534 void SkiaSalGraphicsImpl::invert(sal_uInt32 nPoints, const Point* pPointArray, SalInvert eFlags)
1536 basegfx::B2DPolygon aPolygon;
1537 aPolygon.append(basegfx::B2DPoint(pPointArray[0].getX(), pPointArray[0].getY()), nPoints);
1538 for (sal_uInt32 i = 1; i < nPoints; ++i)
1540 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPointArray[i].getX(), pPointArray[i].getY()));
1542 aPolygon.setClosed(true);
1544 invert(aPolygon, eFlags);
1547 bool SkiaSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*,
1548 sal_uInt32)
1550 return false;
1553 // Create SkImage from a bitmap and possibly an alpha mask (the usual VCL one-minus-alpha),
1554 // with the given target size. Result will be possibly cached, unless disabled.
1555 sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitmap,
1556 const SkiaSalBitmap* alphaBitmap,
1557 const Size targetSize)
1559 sk_sp<SkImage> image;
1560 // GPU-accelerated drawing with SkShader should be fast enough to not need caching.
1561 if (isGPU())
1562 return image;
1563 if (targetSize.IsEmpty())
1564 return image;
1565 if (alphaBitmap && alphaBitmap->IsFullyOpaqueAsAlpha())
1566 alphaBitmap = nullptr; // the alpha can be ignored
1567 // Probably not much point in caching of just doing a copy.
1568 if (alphaBitmap == nullptr && targetSize == bitmap.GetSize())
1569 return image;
1570 // Image too small to be worth caching if not scaling.
1571 if (targetSize == bitmap.GetSize() && targetSize.Width() < 100 && targetSize.Height() < 100)
1572 return image;
1573 // In some cases (tdf#134237) the target size may be very large. In that case it's
1574 // better to rely on Skia to clip and draw only the necessary, rather than prepare
1575 // a very large image only to not use most of it.
1576 const Size drawAreaSize = mClipRegion.GetBoundRect().GetSize();
1577 if (targetSize.Width() > drawAreaSize.Width() || targetSize.Height() > drawAreaSize.Height())
1579 // This is a bit tricky. The condition above just checks that at least a part of the resulting
1580 // image will not be used (it's larger then our drawing area). But this may often happen
1581 // when just scrolling a document with a large image, where the caching may very well be worth it.
1582 // Since the problem is mainly the cost of upscaling and then the size of the resulting bitmap,
1583 // compute a ratio of how much this is going to be scaled up, how much this is larger than
1584 // the drawing area, and then refuse to cache if it's too much.
1585 const double upscaleRatio
1586 = std::max(1.0, 1.0 * targetSize.Width() / bitmap.GetSize().Width()
1587 * targetSize.Height() / bitmap.GetSize().Height());
1588 const double oversizeRatio = 1.0 * targetSize.Width() / drawAreaSize.Width()
1589 * targetSize.Height() / drawAreaSize.Height();
1590 const double ratio = upscaleRatio * oversizeRatio;
1591 if (ratio > 4)
1593 SAL_INFO("vcl.skia.trace", "mergecachebitmaps("
1594 << this << "): not caching, ratio:" << ratio << ", "
1595 << bitmap.GetSize() << "->" << targetSize << " in "
1596 << drawAreaSize);
1597 return image;
1600 // Do not cache the result if it would take most of the cache and thus get evicted soon.
1601 if (targetSize.Width() * targetSize.Height() * 4 > SkiaHelper::maxImageCacheSize() * 0.7)
1602 return image;
1603 OString key;
1604 OStringBuffer keyBuf;
1605 keyBuf.append(targetSize.Width())
1606 .append("x")
1607 .append(targetSize.Height())
1608 .append("_")
1609 .append(bitmap.GetImageKey());
1610 if (alphaBitmap)
1611 keyBuf.append("_").append(alphaBitmap->GetAlphaImageKey());
1612 key = keyBuf.makeStringAndClear();
1613 image = SkiaHelper::findCachedImage(key);
1614 if (image)
1616 assert(image->width() == targetSize.Width() && image->height() == targetSize.Height());
1617 return image;
1619 sk_sp<SkSurface> tmpSurface = SkiaHelper::createSkSurface(
1620 targetSize, alphaBitmap ? kPremul_SkAlphaType : bitmap.alphaType());
1621 if (!tmpSurface)
1622 return nullptr;
1623 SkCanvas* canvas = tmpSurface->getCanvas();
1624 SkAutoCanvasRestore autoRestore(canvas, true);
1625 SkPaint paint;
1626 if (targetSize != bitmap.GetSize())
1628 SkMatrix matrix;
1629 matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / bitmap.GetSize().Width());
1630 matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / bitmap.GetSize().Height());
1631 canvas->concat(matrix);
1632 paint.setFilterQuality(kHigh_SkFilterQuality);
1634 if (alphaBitmap != nullptr)
1636 canvas->clear(SK_ColorTRANSPARENT);
1637 paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut, bitmap.GetSkShader(),
1638 alphaBitmap->GetAlphaSkShader()));
1639 canvas->drawPaint(paint);
1641 else if (bitmap.PreferSkShader())
1643 paint.setShader(bitmap.GetSkShader());
1644 canvas->drawPaint(paint);
1646 else
1647 canvas->drawImage(bitmap.GetSkImage(), 0, 0, &paint);
1648 image = SkiaHelper::makeCheckedImageSnapshot(tmpSurface);
1649 SkiaHelper::addCachedImage(key, image);
1650 return image;
1653 bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
1654 const SalBitmap& rAlphaBitmap)
1656 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1657 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1658 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1659 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1660 // In raster mode use mergeCacheBitmaps(), which will cache the result, avoiding repeated
1661 // alpha blending or scaling. In GPU mode it is simpler to just use SkShader.
1662 SalTwoRect imagePosAry(rPosAry);
1663 Size imageSize = rSourceBitmap.GetSize();
1664 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1665 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1666 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1667 && rPosAry.mnSrcWidth == rSourceBitmap.GetSize().Width()
1668 && rPosAry.mnSrcHeight == rSourceBitmap.GetSize().Height())
1670 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1671 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1672 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1674 sk_sp<SkImage> image = mergeCacheBitmaps(rSkiaSourceBitmap, &rSkiaAlphaBitmap, imageSize);
1675 if (image)
1676 drawImage(imagePosAry, image);
1677 else if (rSkiaAlphaBitmap.IsFullyOpaqueAsAlpha()) // alpha can be ignored
1678 drawBitmap(rPosAry, rSkiaSourceBitmap);
1679 else
1680 drawShader(rPosAry,
1681 SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1682 rSkiaSourceBitmap.GetSkShader(),
1683 rSkiaAlphaBitmap.GetAlphaSkShader()));
1684 return true;
1687 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBitmap& bitmap,
1688 SkBlendMode blendMode)
1690 if (bitmap.PreferSkShader())
1692 drawShader(rPosAry, bitmap.GetSkShader(), blendMode);
1693 return;
1695 // In raster mode use mergeCacheBitmaps(), which will cache the result, avoiding repeated
1696 // scaling. In GPU mode it is simpler to just use SkShader.
1697 SalTwoRect imagePosAry(rPosAry);
1698 Size imageSize = bitmap.GetSize();
1699 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1700 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1701 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1702 && rPosAry.mnSrcWidth == bitmap.GetSize().Width()
1703 && rPosAry.mnSrcHeight == bitmap.GetSize().Height())
1705 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1706 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1707 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1709 sk_sp<SkImage> image = mergeCacheBitmaps(bitmap, nullptr, imageSize);
1710 if (image)
1711 drawImage(imagePosAry, image, blendMode);
1712 else
1713 drawImage(rPosAry, bitmap.GetSkImage(), blendMode);
1716 void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage,
1717 SkBlendMode eBlendMode)
1719 SkRect aSourceRect
1720 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1721 SkRect aDestinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY,
1722 rPosAry.mnDestWidth, rPosAry.mnDestHeight);
1724 SkPaint aPaint;
1725 aPaint.setBlendMode(eBlendMode);
1726 if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1727 aPaint.setFilterQuality(kHigh_SkFilterQuality);
1729 preDraw();
1730 SAL_INFO("vcl.skia.trace",
1731 "drawimage(" << this << "): " << rPosAry << ":" << SkBlendMode_Name(eBlendMode));
1732 addUpdateRegion(aDestinationRect);
1733 getDrawCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect, &aPaint);
1734 ++mPendingOperationsToFlush; // tdf#136369
1735 postDraw();
1738 // SkShader can be used to merge multiple bitmaps with appropriate blend modes (e.g. when
1739 // merging a bitmap with its alpha mask).
1740 void SkiaSalGraphicsImpl::drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader,
1741 SkBlendMode blendMode)
1743 preDraw();
1744 SAL_INFO("vcl.skia.trace", "drawshader(" << this << "): " << rPosAry);
1745 SkRect destinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1746 rPosAry.mnDestHeight);
1747 addUpdateRegion(destinationRect);
1748 SkPaint paint;
1749 paint.setBlendMode(blendMode);
1750 paint.setShader(shader);
1751 if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1752 paint.setFilterQuality(kHigh_SkFilterQuality);
1753 SkCanvas* canvas = getDrawCanvas();
1754 // Scaling needs to be done explicitly using a matrix.
1755 SkAutoCanvasRestore autoRestore(canvas, true);
1756 SkMatrix matrix = SkMatrix::Translate(rPosAry.mnDestX, rPosAry.mnDestY)
1757 * SkMatrix::Scale(1.0 * rPosAry.mnDestWidth / rPosAry.mnSrcWidth,
1758 1.0 * rPosAry.mnDestHeight / rPosAry.mnSrcHeight)
1759 * SkMatrix::Translate(-rPosAry.mnSrcX, -rPosAry.mnSrcY);
1760 #ifndef NDEBUG
1761 // Handle floating point imprecisions, round p1 to 2 decimal places.
1762 auto compareRounded = [](const SkPoint& p1, const SkPoint& p2) {
1763 return rtl::math::round(p1.x(), 2) == p2.x() && rtl::math::round(p1.y(), 2) == p2.y();
1765 #endif
1766 assert(compareRounded(matrix.mapXY(rPosAry.mnSrcX, rPosAry.mnSrcY),
1767 SkPoint::Make(rPosAry.mnDestX, rPosAry.mnDestY)));
1768 assert(compareRounded(
1769 matrix.mapXY(rPosAry.mnSrcX + rPosAry.mnSrcWidth, rPosAry.mnSrcY + rPosAry.mnSrcHeight),
1770 SkPoint::Make(rPosAry.mnDestX + rPosAry.mnDestWidth,
1771 rPosAry.mnDestY + rPosAry.mnDestHeight)));
1772 canvas->concat(matrix);
1773 SkRect sourceRect
1774 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1775 canvas->drawRect(sourceRect, paint);
1776 postDraw();
1779 bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
1780 const basegfx::B2DPoint& rX,
1781 const basegfx::B2DPoint& rY,
1782 const SalBitmap& rSourceBitmap,
1783 const SalBitmap* pAlphaBitmap)
1785 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1786 assert(!pAlphaBitmap || dynamic_cast<const SkiaSalBitmap*>(pAlphaBitmap));
1788 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1789 const SkiaSalBitmap* pSkiaAlphaBitmap = static_cast<const SkiaSalBitmap*>(pAlphaBitmap);
1791 if (pSkiaAlphaBitmap && pSkiaAlphaBitmap->IsFullyOpaqueAsAlpha())
1792 pSkiaAlphaBitmap = nullptr; // the alpha can be ignored
1794 // Setup the image transformation,
1795 // using the rNull, rX, rY points as destinations for the (0,0), (Width,0), (0,Height) source points.
1796 const basegfx::B2DVector aXRel = rX - rNull;
1797 const basegfx::B2DVector aYRel = rY - rNull;
1799 preDraw();
1800 SAL_INFO("vcl.skia.trace", "drawtransformedbitmap(" << this << "): " << rSourceBitmap.GetSize()
1801 << " " << rNull << ":" << rX << ":" << rY);
1803 addUpdateRegion(SkRect::MakeWH(GetWidth(), GetHeight())); // can't tell, use whole area
1804 // In raster mode scaling and alpha blending is still somewhat expensive if done repeatedly,
1805 // so use mergeCacheBitmaps(), which will cache the result if useful.
1806 // It is better to use SkShader if in GPU mode, if the operation is simple or if the temporary
1807 // image would be very large.
1808 sk_sp<SkImage> imageToDraw = mergeCacheBitmaps(
1809 rSkiaBitmap, pSkiaAlphaBitmap, Size(round(aXRel.getLength()), round(aYRel.getLength())));
1810 if (imageToDraw)
1812 SkMatrix matrix;
1813 // Round sizes for scaling, so that sub-pixel differences don't
1814 // trigger unnecessary scaling. Image has already been scaled
1815 // by mergeCacheBitmaps() and we shouldn't scale here again
1816 // unless the drawing is also skewed.
1817 matrix.set(SkMatrix::kMScaleX, round(aXRel.getX()) / imageToDraw->width());
1818 matrix.set(SkMatrix::kMScaleY, round(aYRel.getY()) / imageToDraw->height());
1819 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / imageToDraw->width());
1820 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / imageToDraw->height());
1821 matrix.set(SkMatrix::kMTransX, rNull.getX());
1822 matrix.set(SkMatrix::kMTransY, rNull.getY());
1823 SkCanvas* canvas = getDrawCanvas();
1824 SkAutoCanvasRestore autoRestore(canvas, true);
1825 canvas->concat(matrix);
1826 SkPaint paint;
1827 if (!matrix.isTranslate())
1828 paint.setFilterQuality(kHigh_SkFilterQuality);
1829 canvas->drawImage(imageToDraw, 0, 0, &paint);
1831 else
1833 SkMatrix matrix;
1834 const Size aSize = rSourceBitmap.GetSize();
1835 matrix.set(SkMatrix::kMScaleX, aXRel.getX() / aSize.Width());
1836 matrix.set(SkMatrix::kMScaleY, aYRel.getY() / aSize.Height());
1837 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / aSize.Width());
1838 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / aSize.Height());
1839 matrix.set(SkMatrix::kMTransX, rNull.getX());
1840 matrix.set(SkMatrix::kMTransY, rNull.getY());
1841 SkCanvas* canvas = getDrawCanvas();
1842 SkAutoCanvasRestore autoRestore(canvas, true);
1843 canvas->concat(matrix);
1844 SkPaint paint;
1845 if (!matrix.isTranslate())
1846 paint.setFilterQuality(kHigh_SkFilterQuality);
1847 if (pSkiaAlphaBitmap)
1849 paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1850 rSkiaBitmap.GetSkShader(),
1851 pSkiaAlphaBitmap->GetAlphaSkShader()));
1852 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
1854 else if (rSkiaBitmap.PreferSkShader())
1856 paint.setShader(rSkiaBitmap.GetSkShader());
1857 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
1859 else
1861 canvas->drawImage(rSkiaBitmap.GetSkImage(), 0, 0, &paint);
1864 postDraw();
1865 return true;
1868 bool SkiaSalGraphicsImpl::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
1869 tools::Long nHeight, sal_uInt8 nTransparency)
1871 privateDrawAlphaRect(nX, nY, nWidth, nHeight, nTransparency / 100.0);
1872 return true;
1875 bool SkiaSalGraphicsImpl::drawGradient(const tools::PolyPolygon& rPolyPolygon,
1876 const Gradient& rGradient)
1878 if (rGradient.GetStyle() != GradientStyle::Linear
1879 && rGradient.GetStyle() != GradientStyle::Axial
1880 && rGradient.GetStyle() != GradientStyle::Radial)
1881 return false; // unsupported
1882 if (rGradient.GetSteps() != 0)
1883 return false; // We can't tell Skia how many colors to use in the gradient.
1884 preDraw();
1885 SAL_INFO("vcl.skia.trace", "drawgradient(" << this << "): " << rPolyPolygon.getB2DPolyPolygon()
1886 << ":" << static_cast<int>(rGradient.GetStyle()));
1887 tools::Rectangle boundRect(rPolyPolygon.GetBoundRect());
1888 if (boundRect.IsEmpty())
1889 return true;
1890 SkPath path;
1891 if (rPolyPolygon.IsRect())
1893 // Rect->Polygon conversion loses the right and bottom edge, fix that.
1894 path.addRect(SkRect::MakeXYWH(boundRect.getX(), boundRect.getY(), boundRect.GetWidth(),
1895 boundRect.GetHeight()));
1896 boundRect.AdjustRight(1);
1897 boundRect.AdjustBottom(1);
1899 else
1900 addPolyPolygonToPath(rPolyPolygon.getB2DPolyPolygon(), path);
1901 path.setFillType(SkPathFillType::kEvenOdd);
1902 addUpdateRegion(path.getBounds());
1904 Gradient aGradient(rGradient);
1905 tools::Rectangle aBoundRect;
1906 Point aCenter;
1907 aGradient.SetAngle(aGradient.GetAngle() + Degree10(2700));
1908 aGradient.GetBoundRect(boundRect, aBoundRect, aCenter);
1910 SkColor startColor
1911 = toSkColorWithIntensity(rGradient.GetStartColor(), rGradient.GetStartIntensity());
1912 SkColor endColor = toSkColorWithIntensity(rGradient.GetEndColor(), rGradient.GetEndIntensity());
1914 sk_sp<SkShader> shader;
1915 if (rGradient.GetStyle() == GradientStyle::Linear)
1917 tools::Polygon aPoly(aBoundRect);
1918 aPoly.Rotate(aCenter, aGradient.GetAngle() % Degree10(3600));
1919 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
1920 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
1921 SkColor colors[2] = { startColor, endColor };
1922 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
1923 shader = SkGradientShader::MakeLinear(points, colors, pos, 2, SkTileMode::kClamp);
1925 else if (rGradient.GetStyle() == GradientStyle::Axial)
1927 tools::Polygon aPoly(aBoundRect);
1928 aPoly.Rotate(aCenter, aGradient.GetAngle() % Degree10(3600));
1929 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
1930 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
1931 SkColor colors[3] = { endColor, startColor, endColor };
1932 SkScalar border = SkDoubleToScalar(aGradient.GetBorder() / 100.0);
1933 SkScalar pos[3]
1934 = { std::min<SkScalar>(border, 0.5), 0.5, std::max<SkScalar>(1 - border, 0.5) };
1935 shader = SkGradientShader::MakeLinear(points, colors, pos, 3, SkTileMode::kClamp);
1937 else
1939 // Move the center by (-1,-1) (the default VCL algorithm is a bit off-center that way,
1940 // Skia is the opposite way).
1941 SkPoint center = SkPoint::Make(toSkX(aCenter.X()) - 1, toSkY(aCenter.Y()) - 1);
1942 SkScalar radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
1943 SkColor colors[2] = { endColor, startColor };
1944 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
1945 shader = SkGradientShader::MakeRadial(center, radius, colors, pos, 2, SkTileMode::kClamp);
1948 SkPaint paint;
1949 paint.setAntiAlias(mParent.getAntiAlias());
1950 paint.setShader(shader);
1951 getDrawCanvas()->drawPath(path, paint);
1952 postDraw();
1953 return true;
1956 bool SkiaSalGraphicsImpl::implDrawGradient(const basegfx::B2DPolyPolygon& rPolyPolygon,
1957 const SalGradient& rGradient)
1959 preDraw();
1960 SAL_INFO("vcl.skia.trace",
1961 "impldrawgradient(" << this << "): " << rPolyPolygon << ":" << rGradient.maPoint1
1962 << "->" << rGradient.maPoint2 << ":" << rGradient.maStops.size());
1964 SkPath path;
1965 addPolyPolygonToPath(rPolyPolygon, path);
1966 path.setFillType(SkPathFillType::kEvenOdd);
1967 addUpdateRegion(path.getBounds());
1969 SkPoint points[2]
1970 = { SkPoint::Make(toSkX(rGradient.maPoint1.getX()), toSkY(rGradient.maPoint1.getY())),
1971 SkPoint::Make(toSkX(rGradient.maPoint2.getX()), toSkY(rGradient.maPoint2.getY())) };
1972 std::vector<SkColor> colors;
1973 std::vector<SkScalar> pos;
1974 for (const SalGradientStop& stop : rGradient.maStops)
1976 colors.emplace_back(toSkColor(stop.maColor));
1977 pos.emplace_back(stop.mfOffset);
1979 sk_sp<SkShader> shader = SkGradientShader::MakeLinear(points, colors.data(), pos.data(),
1980 colors.size(), SkTileMode::kDecal);
1981 SkPaint paint;
1982 paint.setAntiAlias(mParent.getAntiAlias());
1983 paint.setShader(shader);
1984 getDrawCanvas()->drawPath(path, paint);
1985 postDraw();
1986 return true;
1989 static double toRadian(Degree10 degree10th) { return (3600 - degree10th.get()) * M_PI / 1800.0; }
1990 static double toCos(Degree10 degree10th) { return SkScalarCos(toRadian(degree10th)); }
1991 static double toSin(Degree10 degree10th) { return SkScalarSin(toRadian(degree10th)); }
1993 void SkiaSalGraphicsImpl::drawGenericLayout(const GenericSalLayout& layout, Color textColor,
1994 const SkFont& font, GlyphOrientation glyphOrientation)
1996 SkiaZone zone;
1997 std::vector<SkGlyphID> glyphIds;
1998 std::vector<SkRSXform> glyphForms;
1999 glyphIds.reserve(256);
2000 glyphForms.reserve(256);
2001 Point aPos;
2002 const GlyphItem* pGlyph;
2003 int nStart = 0;
2004 while (layout.GetNextGlyph(&pGlyph, aPos, nStart))
2006 glyphIds.push_back(pGlyph->glyphId());
2007 Degree10 angle(0); // 10th of degree
2008 if (glyphOrientation == GlyphOrientation::Apply)
2010 angle = layout.GetOrientation();
2011 if (pGlyph->IsVertical())
2012 angle += Degree10(900); // 90 degree
2014 SkRSXform form = SkRSXform::Make(toCos(angle), toSin(angle), aPos.X(), aPos.Y());
2015 glyphForms.emplace_back(std::move(form));
2017 if (glyphIds.empty())
2018 return;
2019 sk_sp<SkTextBlob> textBlob
2020 = SkTextBlob::MakeFromRSXform(glyphIds.data(), glyphIds.size() * sizeof(SkGlyphID),
2021 glyphForms.data(), font, SkTextEncoding::kGlyphID);
2022 preDraw();
2023 SAL_INFO("vcl.skia.trace",
2024 "drawtextblob(" << this << "): " << textBlob->bounds() << ":" << textColor);
2025 addUpdateRegion(textBlob->bounds());
2026 SkPaint paint;
2027 paint.setColor(toSkColor(textColor));
2028 getDrawCanvas()->drawTextBlob(textBlob, 0, 0, paint);
2029 postDraw();
2032 bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
2034 switch (eType)
2036 case OutDevSupportType::B2DDraw:
2037 case OutDevSupportType::TransparentRect:
2038 return true;
2039 default:
2040 return false;
2044 #ifdef DBG_UTIL
2045 void SkiaSalGraphicsImpl::dump(const char* file) const
2047 assert(mSurface.get());
2048 SkiaHelper::dump(mSurface, file);
2050 #endif
2052 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */