1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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>
33 #include <SkGradientShader.h>
36 #include <SkDashPathEffect.h>
37 #include <GrBackendSurface.h>
38 #include <SkTextBlob.h>
39 #include <SkRSXform.h>
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>
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());
64 const bool bClosePath(rPolygon
.isClosed());
65 const bool bHasCurves(rPolygon
.areControlPointsUsed());
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
)
80 // Make sure we loop the last point to first point
81 nCurrentIndex
= nIndex
% nPointCount
;
82 aCurrentPoint
= rPolygon
.getB2DPoint(nCurrentIndex
);
86 rPath
.moveTo(aCurrentPoint
.getX(), aCurrentPoint
.getY());
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;
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;
113 if (aPreviousControlPoint
.equal(aPreviousPoint
))
115 aPreviousControlPoint
116 = aPreviousPoint
+ ((aPreviousControlPoint
- aCurrentPoint
) * 0.0005);
118 if (aCurrentControlPoint
.equal(aCurrentPoint
))
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
;
139 void addPolyPolygonToPath(const basegfx::B2DPolyPolygon
& rPolyPolygon
, SkPath
& rPath
,
140 bool* hasOnlyOrthogonal
= nullptr)
142 const sal_uInt32
nPolygonCount(rPolyPolygon
.count());
144 if (nPolygonCount
== 0)
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
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());
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
)
177 // Make sure we loop the last point to first point
178 nCurrentIndex
= nIndex
% nPointCount
;
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(),
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
),
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
;
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
);
250 virtual ~SkiaFlushIdle() { free(debugname
); }
252 const char* get_debug_name(SkiaSalGraphicsImpl
* pGraphics
)
255 // Idle keeps just a pointer, so we need to store the string
257 OString("skia idle 0x" + OString::number(reinterpret_cast<sal_uIntPtr
>(pGraphics
), 16))
266 virtual void Invoke() override
268 mpGraphics
->performFlush();
270 SetPriority(TaskPriority::HIGHEST
);
274 SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics
& rParent
, SalGeometryProvider
* pProvider
)
276 , mProvider(pProvider
)
278 , mLineColor(SALCOLOR_NONE
)
279 , mFillColor(SALCOLOR_NONE
)
281 , mFlush(new SkiaFlushIdle(this))
282 , mPendingOperationsToFlush(0)
286 SkiaSalGraphicsImpl::~SkiaSalGraphicsImpl()
289 assert(!mWindowContext
);
292 void SkiaSalGraphicsImpl::Init() {}
294 void SkiaSalGraphicsImpl::createSurface()
298 createOffscreenSurface();
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.
307 mFlush
->SetPriority(TaskPriority::POST_PAINT
);
310 void SkiaSalGraphicsImpl::createWindowSurface(bool forceRaster
)
313 assert(!isOffscreen());
315 assert(!mWindowContext
);
316 createWindowContext(forceRaster
);
318 mSurface
= mWindowContext
->getBackbufferSurface();
321 switch (SkiaHelper::renderMethodToUse())
323 case SkiaHelper::RenderVulkan
:
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;
334 SkiaHelper::prefillSurface(mSurface
);
338 bool SkiaSalGraphicsImpl::isOffscreen() const
340 if (mProvider
== nullptr || mProvider
->IsOffScreen())
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)
349 void SkiaSalGraphicsImpl::createOffscreenSurface()
352 assert(isOffscreen());
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
);
367 mIsGPU
= mSurface
->getCanvas()->recordingContext() != nullptr;
376 // Create raster surface as a fallback.
377 mSurface
= SkiaHelper::createSkSurface(width
, height
);
379 assert(!mSurface
->getCanvas()->recordingContext()); // is not GPU-backed
383 void SkiaSalGraphicsImpl::destroySurface()
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.
401 mSurface
->flushAndSubmit();
403 mWindowContext
.reset();
407 void SkiaSalGraphicsImpl::DeInit() { destroySurface(); }
409 void SkiaSalGraphicsImpl::preDraw()
411 assert(comphelper::SolarMutex::get()->IsCurrentThread());
412 SkiaZone::enter(); // matched in postDraw()
414 checkPendingDrawing();
417 void SkiaSalGraphicsImpl::postDraw()
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.");
441 // Unrecoverable problem.
442 if (context
->abandoned())
444 SAL_WARN("vcl.skia", "GPU context has been abandoned, aborting.");
450 void SkiaSalGraphicsImpl::scheduleFlush()
454 if (!Application::IsInExecute())
455 performFlush(); // otherwise nothing would trigger idle rendering
456 else if (!mFlush
->IsActive())
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()
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
;
487 snapshot
= SkiaHelper::makeCheckedImageSnapshot(mSurface
);
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())
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)
515 void SkiaSalGraphicsImpl::flushDrawing()
519 checkPendingDrawing();
522 mSurface
->flushAndSubmit();
523 mPendingOperationsToFlush
= 0;
526 bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region
& region
)
528 if (mClipRegion
== region
)
531 checkPendingDrawing();
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
541 assert(canvas
->getSaveCount() == 2); // = there is just one save()
544 setCanvasClipRegion(canvas
, region
);
548 void SkiaSalGraphicsImpl::setCanvasClipRegion(SkCanvas
* canvas
, const vcl::Region
& region
)
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();
587 void SkiaSalGraphicsImpl::SetFillColor()
589 checkPendingDrawing();
590 mFillColor
= SALCOLOR_NONE
;
593 void SkiaSalGraphicsImpl::SetFillColor(Color nColor
)
595 checkPendingDrawing();
599 void SkiaSalGraphicsImpl::SetXORMode(bool set
, bool)
603 checkPendingDrawing();
604 SAL_INFO("vcl.skia.trace", "setxormode(" << this << "): " << set
);
606 mXorRegion
.setEmpty();
612 SkCanvas
* SkiaSalGraphicsImpl::getXorCanvas()
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.
621 // Use unpremultiplied alpha (see xor applying in applyXor()).
622 if (!mXorBitmap
.tryAllocPixels(mSurface
->imageInfo().makeAlphaType(kUnpremul_SkAlphaType
)))
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
638 if (!mSurface
|| !mXorCanvas
639 || !mXorRegion
.op(SkIRect::MakeXYWH(0, 0, mSurface
->width(), mSurface
->height()),
640 SkRegion::kIntersect_Op
))
642 mXorRegion
.setEmpty();
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
)))
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
679 surfaceBitmap
.notifyPixelsChanged();
680 mSurface
->getCanvas()->drawBitmapRect(surfaceBitmap
, mXorRegion
.getBounds(),
681 SkRect::Make(mXorRegion
.getBounds()), &paint
);
684 mXorRegion
.setEmpty();
687 void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor
)
689 checkPendingDrawing();
692 case SalROPColor::N0
:
693 mLineColor
= Color(0, 0, 0);
695 case SalROPColor::N1
:
696 mLineColor
= Color(0xff, 0xff, 0xff);
698 case SalROPColor::Invert
:
699 mLineColor
= Color(0xff, 0xff, 0xff);
704 void SkiaSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor
)
706 checkPendingDrawing();
709 case SalROPColor::N0
:
710 mFillColor
= Color(0, 0, 0);
712 case SalROPColor::N1
:
713 mFillColor
= Color(0xff, 0xff, 0xff);
715 case SalROPColor::Invert
:
716 mFillColor
= Color(0xff, 0xff, 0xff);
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
)
731 SAL_INFO("vcl.skia.trace", "drawpixel(" << this << "): " << Point(nX
, nY
) << ":" << nColor
);
732 addUpdateRegion(SkRect::MakeXYWH(nX
, nY
, 1, 1));
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
);
741 void SkiaSalGraphicsImpl::drawLine(tools::Long nX1
, tools::Long nY1
, tools::Long nX2
,
744 if (mLineColor
== SALCOLOR_NONE
)
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());
751 paint
.setColor(toSkColor(mLineColor
));
752 paint
.setAntiAlias(mParent
.getAntiAlias());
753 getDrawCanvas()->drawLine(toSkX(nX1
), toSkY(nY1
), toSkX(nX2
), toSkY(nY2
), paint
);
757 void SkiaSalGraphicsImpl::privateDrawAlphaRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
758 tools::Long nHeight
, double fTransparency
,
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();
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)),
793 void SkiaSalGraphicsImpl::drawRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
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
];
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)
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
))
867 performDrawPolyPolygon(aPolyPolygon
, fTransparency
, mParent
.getAntiAlias());
871 void SkiaSalGraphicsImpl::performDrawPolyPolygon(const basegfx::B2DPolyPolygon
& aPolyPolygon
,
872 double fTransparency
, bool useAA
)
877 bool hasOnlyOrthogonal
= true;
878 addPolyPolygonToPath(aPolyPolygon
, polygonPath
, &hasOnlyOrthogonal
);
879 polygonPath
.setFillType(SkPathFillType::kEvenOdd
);
880 addUpdateRegion(polygonPath
.getBounds());
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
);
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();
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());
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())
963 // Only filled polygons without an outline are problematic.
964 if (mFillColor
== SALCOLOR_NONE
|| mLineColor
!= SALCOLOR_NONE
)
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)
970 // If the polygon is not closed, it doesn't mark an area to be filled.
971 if (!aPolyPolygon
.isClosed())
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
))
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())
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
;
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);
1041 for (basegfx::B2DPolyPolygon
& p
: polygons
)
1042 roundPolygonPoints(p
);
1043 performDrawPolyPolygon(basegfx::utils::mergeToSinglePolyPolygon(polygons
), transparency
,
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
)
1063 SAL_INFO("vcl.skia.trace", "drawpolyline(" << this << "): " << rPolyLine
<< ":" << mLineColor
);
1065 // tdf#124848 get correct LineWidth in discrete coordinates,
1066 if (fLineWidth
== 0) // hairline
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
);
1081 SkPaint::Join eSkLineJoin
= SkPaint::kMiter_Join
;
1084 case basegfx::B2DLineJoin::Bevel
:
1085 eSkLineJoin
= SkPaint::kBevel_Join
;
1087 case basegfx::B2DLineJoin::Round
:
1088 eSkLineJoin
= SkPaint::kRound_Join
;
1090 case basegfx::B2DLineJoin::NONE
:
1091 case basegfx::B2DLineJoin::Miter
:
1092 eSkLineJoin
= SkPaint::kMiter_Join
;
1096 // convert miter minimum angle to miter limit
1097 double fMiterLimit
= 1.0 / std::sin(fMiterMinimumAngle
/ 2.0);
1100 SkPaint::Cap
eSkLineCap(SkPaint::kButt_Cap
);
1104 case css::drawing::LineCap_ROUND
:
1105 eSkLineCap
= SkPaint::kRound_Cap
;
1107 case css::drawing::LineCap_SQUARE
:
1108 eSkLineCap
= SkPaint::kSquare_Cap
;
1110 default: // css::drawing::LineCap_BUTT:
1111 eSkLineCap
= SkPaint::kButt_Cap
;
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)
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
);
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
;
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
);
1176 bool SkiaSalGraphicsImpl::drawPolyLineBezier(sal_uInt32
, const Point
*, const PolyFlags
*)
1181 bool SkiaSalGraphicsImpl::drawPolygonBezier(sal_uInt32
, const Point
*, const PolyFlags
*)
1186 bool SkiaSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32
, const sal_uInt32
*, const Point
* const*,
1187 const PolyFlags
* const*)
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
,
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
))
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
);
1213 // SkCanvas::draw() cannot do a subrectangle, so clip.
1215 canvas
->clipRect(SkRect::MakeXYWH(nDestX
, nDestY
, nSrcWidth
, nSrcHeight
));
1217 paint
.setBlendMode(SkBlendMode::kSrc
); // copy as is, including alpha
1218 surface
->draw(canvas
, nDestX
- nSrcX
, nDestY
- nSrcY
, &paint
);
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
)
1229 SAL_INFO("vcl.skia.trace", "copyarea("
1230 << this << "): " << Point(nSrcX
, nSrcY
) << "->"
1231 << SkIRect::MakeXYWH(nDestX
, nDestY
, nSrcWidth
, nSrcHeight
));
1233 addUpdateRegion(SkRect::MakeXYWH(nDestX
, nDestY
, nSrcWidth
, nSrcHeight
));
1234 ::copyArea(getDrawCanvas(), mSurface
, nDestX
, nDestY
, nSrcX
, nSrcY
, nSrcWidth
, nSrcHeight
,
1235 !isGPU(), !isGPU());
1239 void SkiaSalGraphicsImpl::copyBits(const SalTwoRect
& rPosAry
, SalGraphics
* pSrcGraphics
)
1242 SkiaSalGraphicsImpl
* src
;
1245 assert(dynamic_cast<SkiaSalGraphicsImpl
*>(pSrcGraphics
->GetImpl()));
1246 src
= static_cast<SkiaSalGraphicsImpl
*>(pSrcGraphics
->GetImpl());
1247 src
->checkSurface();
1248 src
->flushDrawing();
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
{
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(),
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
);
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
),
1296 bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rBitmap
)
1298 if (checkInvalidSourceOrDestination(rPosAry
))
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
);
1319 drawBitmap(rPosAry
, rSkiaBitmap
, SkBlendMode::kMultiply
);
1323 bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect
& rPosAry
,
1324 const SalBitmap
& rSourceBitmap
,
1325 const SalBitmap
& rMaskBitmap
,
1326 const SalBitmap
& rAlphaBitmap
)
1328 if (checkInvalidSourceOrDestination(rPosAry
))
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
);
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
);
1366 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
)
1368 if (checkInvalidSourceOrDestination(rPosAry
))
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
,
1386 assert(dynamic_cast<const SkiaSalBitmap
*>(&rSalBitmap
));
1387 const SkiaSalBitmap
& skiaBitmap
= static_cast<const SkiaSalBitmap
&>(rSalBitmap
);
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
)
1399 SAL_INFO("vcl.skia.trace",
1400 "getbitmap(" << this << "): " << SkIRect::MakeXYWH(nX
, nY
, nWidth
, nHeight
));
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
)
1414 SAL_INFO("vcl.skia.trace", "getpixel(" << this << "): " << Point(nX
, nY
));
1416 // This is presumably slow, but getPixel() should be generally used only by unit tests.
1418 if (!bitmap
.tryAllocN32Pixels(GetWidth(), GetHeight()))
1420 if (!mSurface
->readPixels(bitmap
, 0, 0))
1422 return fromSkColor(bitmap
.getColor(nX
, nY
));
1425 void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon
const& rPoly
, SalInvert eFlags
)
1428 SAL_INFO("vcl.skia.trace", "invert(" << this << "): " << rPoly
<< ":" << int(eFlags
));
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.
1435 = (isGPU() && SkiaHelper::getVendor() == DriverBlocklist::VendorIntel
&& !mXorMode
);
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
1446 SkAutoCanvasRestore
autoRestore(getDrawCanvas(), true);
1447 getDrawCanvas()->clipRect(aPath
.getBounds(), SkClipOp::kIntersect
, false);
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
);
1456 getDrawCanvas()->drawPath(aPath
, aPaint
);
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());
1465 copy
.setBlendMode(SkBlendMode::kSrc
);
1467 surface
->getCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(mSurface
),
1469 aPath
.offset(-area
.x(), -area
.y());
1470 surface
->getCanvas()->drawPath(aPath
, aPaint
);
1471 getDrawCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(surface
), size
,
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
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
));
1504 getDrawCanvas()->drawPath(aPath
, aPaint
);
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());
1513 copy
.setBlendMode(SkBlendMode::kSrc
);
1515 surface
->getCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(mSurface
),
1517 aPath
.offset(-area
.x(), -area
.y());
1518 surface
->getCanvas()->drawPath(aPath
, aPaint
);
1519 getDrawCanvas()->drawImageRect(SkiaHelper::makeCheckedImageSnapshot(surface
), size
,
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*,
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.
1563 if (targetSize
.IsEmpty())
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())
1570 // Image too small to be worth caching if not scaling.
1571 if (targetSize
== bitmap
.GetSize() && targetSize
.Width() < 100 && targetSize
.Height() < 100)
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
;
1593 SAL_INFO("vcl.skia.trace", "mergecachebitmaps("
1594 << this << "): not caching, ratio:" << ratio
<< ", "
1595 << bitmap
.GetSize() << "->" << targetSize
<< " in "
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)
1604 OStringBuffer keyBuf
;
1605 keyBuf
.append(targetSize
.Width())
1607 .append(targetSize
.Height())
1609 .append(bitmap
.GetImageKey());
1611 keyBuf
.append("_").append(alphaBitmap
->GetAlphaImageKey());
1612 key
= keyBuf
.makeStringAndClear();
1613 image
= SkiaHelper::findCachedImage(key
);
1616 assert(image
->width() == targetSize
.Width() && image
->height() == targetSize
.Height());
1619 sk_sp
<SkSurface
> tmpSurface
= SkiaHelper::createSkSurface(
1620 targetSize
, alphaBitmap
? kPremul_SkAlphaType
: bitmap
.alphaType());
1623 SkCanvas
* canvas
= tmpSurface
->getCanvas();
1624 SkAutoCanvasRestore
autoRestore(canvas
, true);
1626 if (targetSize
!= bitmap
.GetSize())
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
);
1647 canvas
->drawImage(bitmap
.GetSkImage(), 0, 0, &paint
);
1648 image
= SkiaHelper::makeCheckedImageSnapshot(tmpSurface
);
1649 SkiaHelper::addCachedImage(key
, 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
);
1676 drawImage(imagePosAry
, image
);
1677 else if (rSkiaAlphaBitmap
.IsFullyOpaqueAsAlpha()) // alpha can be ignored
1678 drawBitmap(rPosAry
, rSkiaSourceBitmap
);
1681 SkShaders::Blend(SkBlendMode::kDstOut
, // VCL alpha is one-minus-alpha.
1682 rSkiaSourceBitmap
.GetSkShader(),
1683 rSkiaAlphaBitmap
.GetAlphaSkShader()));
1687 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect
& rPosAry
, const SkiaSalBitmap
& bitmap
,
1688 SkBlendMode blendMode
)
1690 if (bitmap
.PreferSkShader())
1692 drawShader(rPosAry
, bitmap
.GetSkShader(), blendMode
);
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
);
1711 drawImage(imagePosAry
, image
, blendMode
);
1713 drawImage(rPosAry
, bitmap
.GetSkImage(), blendMode
);
1716 void SkiaSalGraphicsImpl::drawImage(const SalTwoRect
& rPosAry
, const sk_sp
<SkImage
>& aImage
,
1717 SkBlendMode eBlendMode
)
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
);
1725 aPaint
.setBlendMode(eBlendMode
);
1726 if (rPosAry
.mnSrcWidth
!= rPosAry
.mnDestWidth
|| rPosAry
.mnSrcHeight
!= rPosAry
.mnDestHeight
)
1727 aPaint
.setFilterQuality(kHigh_SkFilterQuality
);
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
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
)
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
);
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
);
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();
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
);
1774 = SkRect::MakeXYWH(rPosAry
.mnSrcX
, rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
);
1775 canvas
->drawRect(sourceRect
, paint
);
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
;
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())));
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
);
1827 if (!matrix
.isTranslate())
1828 paint
.setFilterQuality(kHigh_SkFilterQuality
);
1829 canvas
->drawImage(imageToDraw
, 0, 0, &paint
);
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
);
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
);
1861 canvas
->drawImage(rSkiaBitmap
.GetSkImage(), 0, 0, &paint
);
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);
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.
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())
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);
1900 addPolyPolygonToPath(rPolyPolygon
.getB2DPolyPolygon(), path
);
1901 path
.setFillType(SkPathFillType::kEvenOdd
);
1902 addUpdateRegion(path
.getBounds());
1904 Gradient
aGradient(rGradient
);
1905 tools::Rectangle aBoundRect
;
1907 aGradient
.SetAngle(aGradient
.GetAngle() + Degree10(2700));
1908 aGradient
.GetBoundRect(boundRect
, aBoundRect
, aCenter
);
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);
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
);
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
);
1949 paint
.setAntiAlias(mParent
.getAntiAlias());
1950 paint
.setShader(shader
);
1951 getDrawCanvas()->drawPath(path
, paint
);
1956 bool SkiaSalGraphicsImpl::implDrawGradient(const basegfx::B2DPolyPolygon
& rPolyPolygon
,
1957 const SalGradient
& rGradient
)
1960 SAL_INFO("vcl.skia.trace",
1961 "impldrawgradient(" << this << "): " << rPolyPolygon
<< ":" << rGradient
.maPoint1
1962 << "->" << rGradient
.maPoint2
<< ":" << rGradient
.maStops
.size());
1965 addPolyPolygonToPath(rPolyPolygon
, path
);
1966 path
.setFillType(SkPathFillType::kEvenOdd
);
1967 addUpdateRegion(path
.getBounds());
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
);
1982 paint
.setAntiAlias(mParent
.getAntiAlias());
1983 paint
.setShader(shader
);
1984 getDrawCanvas()->drawPath(path
, paint
);
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
)
1997 std::vector
<SkGlyphID
> glyphIds
;
1998 std::vector
<SkRSXform
> glyphForms
;
1999 glyphIds
.reserve(256);
2000 glyphForms
.reserve(256);
2002 const GlyphItem
* pGlyph
;
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())
2019 sk_sp
<SkTextBlob
> textBlob
2020 = SkTextBlob::MakeFromRSXform(glyphIds
.data(), glyphIds
.size() * sizeof(SkGlyphID
),
2021 glyphForms
.data(), font
, SkTextEncoding::kGlyphID
);
2023 SAL_INFO("vcl.skia.trace",
2024 "drawtextblob(" << this << "): " << textBlob
->bounds() << ":" << textColor
);
2025 addUpdateRegion(textBlob
->bounds());
2027 paint
.setColor(toSkColor(textColor
));
2028 getDrawCanvas()->drawTextBlob(textBlob
, 0, 0, paint
);
2032 bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType eType
) const
2036 case OutDevSupportType::B2DDraw
:
2037 case OutDevSupportType::TransparentRect
:
2045 void SkiaSalGraphicsImpl::dump(const char* file
) const
2047 assert(mSurface
.get());
2048 SkiaHelper::dump(mSurface
, file
);
2052 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */