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>
48 using namespace SkiaHelper
;
52 // Create Skia Path from B2DPolygon
53 // Note that polygons generally have the complication that when used
54 // for area (fill) operations they usually miss the right-most and
55 // bottom-most line of pixels of the bounding rectangle (see
56 // https://lists.freedesktop.org/archives/libreoffice/2019-November/083709.html).
57 // So be careful with rectangle->polygon conversions (generally avoid them).
58 void addPolygonToPath(const basegfx::B2DPolygon
& rPolygon
, SkPath
& rPath
, sal_uInt32 nFirstIndex
,
59 sal_uInt32 nLastIndex
, const sal_uInt32 nPointCount
, const bool bClosePath
,
60 const bool bHasCurves
, bool* hasOnlyOrthogonal
= nullptr)
62 assert(nFirstIndex
< nPointCount
);
63 assert(nLastIndex
<= nPointCount
);
69 sal_uInt32 nPreviousIndex
= nFirstIndex
== 0 ? nPointCount
- 1 : nFirstIndex
- 1;
70 basegfx::B2DPoint aPreviousPoint
= rPolygon
.getB2DPoint(nPreviousIndex
);
72 for (sal_uInt32 nIndex
= nFirstIndex
; nIndex
<= nLastIndex
; nIndex
++)
74 if (nIndex
== nPointCount
&& !bClosePath
)
77 // Make sure we loop the last point to first point
78 sal_uInt32 nCurrentIndex
= nIndex
% nPointCount
;
79 basegfx::B2DPoint aCurrentPoint
= rPolygon
.getB2DPoint(nCurrentIndex
);
83 rPath
.moveTo(aCurrentPoint
.getX(), aCurrentPoint
.getY());
88 rPath
.lineTo(aCurrentPoint
.getX(), aCurrentPoint
.getY());
89 // If asked for, check whether the polygon has a line that is not
90 // strictly horizontal or vertical.
91 if (hasOnlyOrthogonal
!= nullptr && aCurrentPoint
.getX() != aPreviousPoint
.getX()
92 && aCurrentPoint
.getY() != aPreviousPoint
.getY())
93 *hasOnlyOrthogonal
= false;
97 basegfx::B2DPoint aPreviousControlPoint
= rPolygon
.getNextControlPoint(nPreviousIndex
);
98 basegfx::B2DPoint aCurrentControlPoint
= rPolygon
.getPrevControlPoint(nCurrentIndex
);
100 if (aPreviousControlPoint
.equal(aPreviousPoint
)
101 && aCurrentControlPoint
.equal(aCurrentPoint
))
103 rPath
.lineTo(aCurrentPoint
.getX(), aCurrentPoint
.getY()); // a straight line
104 if (hasOnlyOrthogonal
!= nullptr && aCurrentPoint
.getX() != aPreviousPoint
.getX()
105 && aCurrentPoint
.getY() != aPreviousPoint
.getY())
106 *hasOnlyOrthogonal
= false;
110 if (aPreviousControlPoint
.equal(aPreviousPoint
))
112 aPreviousControlPoint
113 = aPreviousPoint
+ ((aPreviousControlPoint
- aCurrentPoint
) * 0.0005);
115 if (aCurrentControlPoint
.equal(aCurrentPoint
))
118 = aCurrentPoint
+ ((aCurrentControlPoint
- aPreviousPoint
) * 0.0005);
120 rPath
.cubicTo(aPreviousControlPoint
.getX(), aPreviousControlPoint
.getY(),
121 aCurrentControlPoint
.getX(), aCurrentControlPoint
.getY(),
122 aCurrentPoint
.getX(), aCurrentPoint
.getY());
123 if (hasOnlyOrthogonal
!= nullptr)
124 *hasOnlyOrthogonal
= false;
127 aPreviousPoint
= aCurrentPoint
;
128 nPreviousIndex
= nCurrentIndex
;
130 if (bClosePath
&& nFirstIndex
== 0 && nLastIndex
== nPointCount
)
136 void addPolygonToPath(const basegfx::B2DPolygon
& rPolygon
, SkPath
& rPath
,
137 bool* hasOnlyOrthogonal
= nullptr)
139 addPolygonToPath(rPolygon
, rPath
, 0, rPolygon
.count(), rPolygon
.count(), rPolygon
.isClosed(),
140 rPolygon
.areControlPointsUsed(), hasOnlyOrthogonal
);
143 void addPolyPolygonToPath(const basegfx::B2DPolyPolygon
& rPolyPolygon
, SkPath
& rPath
,
144 bool* hasOnlyOrthogonal
= nullptr)
146 const sal_uInt32
nPolygonCount(rPolyPolygon
.count());
148 if (nPolygonCount
== 0)
151 sal_uInt32 nPointCount
= 0;
152 for (const auto& rPolygon
: rPolyPolygon
)
153 nPointCount
+= rPolygon
.count() * 3; // because cubicTo is 3 elements
154 rPath
.incReserve(nPointCount
);
156 for (const auto& rPolygon
: rPolyPolygon
)
158 addPolygonToPath(rPolygon
, rPath
, hasOnlyOrthogonal
);
162 // Check if the given polygon contains a straight line. If not, it consists
164 bool polygonContainsLine(const basegfx::B2DPolyPolygon
& rPolyPolygon
)
166 if (!rPolyPolygon
.areControlPointsUsed())
167 return true; // no curves at all
168 for (const auto& rPolygon
: rPolyPolygon
)
170 const sal_uInt32
nPointCount(rPolygon
.count());
173 const bool bClosePath(rPolygon
.isClosed());
175 sal_uInt32 nCurrentIndex
= 0;
176 sal_uInt32 nPreviousIndex
= nPointCount
- 1;
178 basegfx::B2DPoint aCurrentPoint
;
179 basegfx::B2DPoint aPreviousPoint
;
181 for (sal_uInt32 nIndex
= 0; nIndex
<= nPointCount
; nIndex
++)
183 if (nIndex
== nPointCount
&& !bClosePath
)
186 // Make sure we loop the last point to first point
187 nCurrentIndex
= nIndex
% nPointCount
;
192 basegfx::B2DPoint aPreviousControlPoint
193 = rPolygon
.getNextControlPoint(nPreviousIndex
);
194 basegfx::B2DPoint aCurrentControlPoint
195 = rPolygon
.getPrevControlPoint(nCurrentIndex
);
197 if (aPreviousControlPoint
.equal(aPreviousPoint
)
198 && aCurrentControlPoint
.equal(aCurrentPoint
))
200 return true; // found a straight line
203 aPreviousPoint
= aCurrentPoint
;
204 nPreviousIndex
= nCurrentIndex
;
207 return false; // no straight line found
210 // returns true if the source or destination rectangles are invalid
211 bool checkInvalidSourceOrDestination(SalTwoRect
const& rPosAry
)
213 return rPosAry
.mnSrcWidth
<= 0 || rPosAry
.mnSrcHeight
<= 0 || rPosAry
.mnDestWidth
<= 0
214 || rPosAry
.mnDestHeight
<= 0;
217 } // end anonymous namespace
219 // Class that triggers flushing the backing buffer when idle.
220 class SkiaFlushIdle
: public Idle
222 SkiaSalGraphicsImpl
* mpGraphics
;
228 explicit SkiaFlushIdle(SkiaSalGraphicsImpl
* pGraphics
)
229 : Idle(get_debug_name(pGraphics
))
230 , mpGraphics(pGraphics
)
232 // We don't want to be swapping before we've painted.
233 SetPriority(TaskPriority::POST_PAINT
);
236 virtual ~SkiaFlushIdle() { free(debugname
); }
238 const char* get_debug_name(SkiaSalGraphicsImpl
* pGraphics
)
241 // Idle keeps just a pointer, so we need to store the string
243 OString("skia idle 0x" + OString::number(reinterpret_cast<sal_uIntPtr
>(pGraphics
), 16))
252 virtual void Invoke() override
254 mpGraphics
->performFlush();
256 SetPriority(TaskPriority::HIGHEST
);
260 SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics
& rParent
, SalGeometryProvider
* pProvider
)
262 , mProvider(pProvider
)
264 , mLineColor(SALCOLOR_NONE
)
265 , mFillColor(SALCOLOR_NONE
)
267 , mFlush(new SkiaFlushIdle(this))
268 , mPendingOperationsToFlush(0)
273 SkiaSalGraphicsImpl::~SkiaSalGraphicsImpl()
276 assert(!mWindowContext
);
279 void SkiaSalGraphicsImpl::Init() {}
281 void SkiaSalGraphicsImpl::createSurface()
285 createOffscreenSurface();
287 createWindowSurface();
288 mClipRegion
= vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight()));
289 mDirtyRect
= SkIRect::MakeWH(GetWidth(), GetHeight());
290 setCanvasScalingAndClipping();
292 // We don't want to be swapping before we've painted.
294 mFlush
->SetPriority(TaskPriority::POST_PAINT
);
297 void SkiaSalGraphicsImpl::createWindowSurface(bool forceRaster
)
300 assert(!isOffscreen());
302 createWindowSurfaceInternal(forceRaster
);
305 switch (renderMethodToUse())
309 "cannot create Vulkan GPU window surface, falling back to Raster");
310 destroySurface(); // destroys also WindowContext
311 return createWindowSurface(true); // try again
314 "cannot create Metal GPU window surface, falling back to Raster");
315 destroySurface(); // destroys also WindowContext
316 return createWindowSurface(true); // try again
318 abort(); // This should not really happen, do not even try to cope with it.
321 mIsGPU
= mSurface
->getCanvas()->recordingContext() != nullptr;
323 prefillSurface(mSurface
);
327 bool SkiaSalGraphicsImpl::isOffscreen() const
329 if (mProvider
== nullptr || mProvider
->IsOffScreen())
331 // HACK: Sometimes (tdf#131939, tdf#138022, tdf#140288) VCL passes us a zero-sized window,
332 // and zero size is invalid for Skia, so force offscreen surface, where we handle this.
333 if (GetWidth() <= 0 || GetHeight() <= 0)
338 void SkiaSalGraphicsImpl::createOffscreenSurface()
341 assert(isOffscreen());
343 // HACK: See isOffscreen().
344 int width
= std::max(1, GetWidth());
345 int height
= std::max(1, GetHeight());
346 // We need to use window scaling even for offscreen surfaces, because the common usage is rendering something
347 // into an offscreen surface and then copy it to a window, so without scaling here the result would be originally
348 // drawn without scaling and only upscaled when drawing to a window.
349 mScaling
= getWindowScaling();
350 mSurface
= createSkSurface(width
* mScaling
, height
* mScaling
);
352 mIsGPU
= mSurface
->getCanvas()->recordingContext() != nullptr;
355 void SkiaSalGraphicsImpl::destroySurface()
360 // check setClipRegion() invariant
361 assert(mSurface
->getCanvas()->getSaveCount() == 3);
362 // if this fails, something forgot to use SkAutoCanvasRestore
363 assert(mSurface
->getCanvas()->getTotalMatrix() == SkMatrix::Scale(mScaling
, mScaling
));
365 // If we use e.g. Vulkan, we must destroy the surface before the context,
366 // otherwise destroying the surface will reference the context. This is
367 // handled by calling destroySurface() before destroying the context.
368 // However we also need to flush the surface before destroying it,
369 // otherwise when destroying the context later there still could be queued
370 // commands referring to the surface data. This is probably a Skia bug,
371 // but work around it here.
373 mSurface
->flushAndSubmit();
375 mWindowContext
.reset();
380 void SkiaSalGraphicsImpl::performFlush()
386 if (mDirtyRect
.intersect(SkIRect::MakeWH(GetWidth(), GetHeight())))
387 flushSurfaceToWindowContext();
388 mDirtyRect
.setEmpty();
392 void SkiaSalGraphicsImpl::flushSurfaceToWindowContext()
394 sk_sp
<SkSurface
> screenSurface
= mWindowContext
->getBackbufferSurface();
395 if (screenSurface
!= mSurface
)
397 // GPU-based window contexts require calling getBackbufferSurface()
398 // for every swapBuffers(), for this reason mSurface is an offscreen surface
399 // where we keep the contents (LO does not do full redraws).
400 // So here blit the surface to the window context surface and then swap it.
401 assert(isGPU()); // Raster should always draw directly to backbuffer to save copying
403 paint
.setBlendMode(SkBlendMode::kSrc
); // copy as is
404 // We ignore mDirtyRect here, and mSurface already is in screenSurface coordinates,
405 // so no transformation needed.
406 screenSurface
->getCanvas()->drawImage(makeCheckedImageSnapshot(mSurface
), 0, 0,
407 SkSamplingOptions(), &paint
);
408 screenSurface
->flushAndSubmit(); // Otherwise the window is not drawn sometimes.
409 mWindowContext
->swapBuffers(nullptr); // Must swap the entire surface.
413 // For raster mode use directly the backbuffer surface, it's just a bitmap
414 // surface anyway, and for those there's no real requirement to call
415 // getBackbufferSurface() repeatedly. Using our own surface would duplicate
416 // memory and cost time copying pixels around.
418 SkIRect dirtyRect
= mDirtyRect
;
419 if (mScaling
!= 1) // Adjust to mSurface coordinates if needed.
420 dirtyRect
= scaleRect(dirtyRect
, mScaling
);
421 mWindowContext
->swapBuffers(&dirtyRect
);
425 void SkiaSalGraphicsImpl::DeInit() { destroySurface(); }
427 void SkiaSalGraphicsImpl::preDraw()
429 assert(comphelper::SolarMutex::get()->IsCurrentThread());
430 SkiaZone::enter(); // matched in postDraw()
432 checkPendingDrawing();
435 void SkiaSalGraphicsImpl::postDraw()
438 // Skia (at least when using Vulkan) queues drawing commands and executes them only later.
439 // But tdf#136369 leads to creating and queueing many tiny bitmaps, which makes
440 // Skia slow, and may make it even run out of memory. So force a flush if such
441 // a problematic operation has been performed too many times without a flush.
442 if (mPendingOperationsToFlush
> 1000)
444 mSurface
->flushAndSubmit();
445 mPendingOperationsToFlush
= 0;
447 SkiaZone::leave(); // matched in preDraw()
448 // If there's a problem with the GPU context, abort.
449 if (GrDirectContext
* context
= GrAsDirectContext(mSurface
->getCanvas()->recordingContext()))
451 // Running out of memory on the GPU technically could be possibly recoverable,
452 // but we don't know the exact status of the surface (and what has or has not been drawn to it),
453 // so in practice this is unrecoverable without possible data loss.
454 if (context
->oomed())
456 SAL_WARN("vcl.skia", "GPU context has run out of memory, aborting.");
459 // Unrecoverable problem.
460 if (context
->abandoned())
462 SAL_WARN("vcl.skia", "GPU context has been abandoned, aborting.");
468 void SkiaSalGraphicsImpl::scheduleFlush()
472 if (!Application::IsInExecute())
473 performFlush(); // otherwise nothing would trigger idle rendering
474 else if (!mFlush
->IsActive())
479 // VCL can sometimes resize us without telling us, update the surface if needed.
480 // Also create the surface on demand if it has not been created yet (it is a waste
481 // to create it in Init() if it gets recreated later anyway).
482 void SkiaSalGraphicsImpl::checkSurface()
487 SAL_INFO("vcl.skia.trace",
488 "create(" << this << "): " << Size(mSurface
->width(), mSurface
->height()));
490 else if (GetWidth() * mScaling
!= mSurface
->width()
491 || GetHeight() * mScaling
!= mSurface
->height())
493 if (!avoidRecreateByResize())
495 Size
oldSize(mSurface
->width(), mSurface
->height());
496 // Recreating a surface means that the old SkSurface contents will be lost.
497 // But if a window has been resized the windowing system may send repaint events
498 // only for changed parts and VCL would not repaint the whole area, assuming
499 // that some parts have not changed (this is what seems to cause tdf#131952).
500 // So carry over the old contents for windows, even though generally everything
501 // will be usually repainted anyway.
502 sk_sp
<SkImage
> snapshot
;
506 snapshot
= makeCheckedImageSnapshot(mSurface
);
515 paint
.setBlendMode(SkBlendMode::kSrc
); // copy as is
516 // Scaling by current mScaling is active, undo that. We assume that the scaling
518 resetCanvasScalingAndClipping();
519 mSurface
->getCanvas()->drawImage(snapshot
, 0, 0, SkSamplingOptions(), &paint
);
520 setCanvasScalingAndClipping();
522 SAL_INFO("vcl.skia.trace", "recreate(" << this << "): old " << oldSize
<< " new "
523 << Size(mSurface
->width(), mSurface
->height())
525 << Size(GetWidth(), GetHeight()));
530 bool SkiaSalGraphicsImpl::avoidRecreateByResize() const
532 // Keep the old surface if VCL sends us a broken size (see isOffscreen()).
533 if (GetWidth() == 0 || GetHeight() == 0)
538 void SkiaSalGraphicsImpl::flushDrawing()
542 checkPendingDrawing();
545 mSurface
->flushAndSubmit();
546 mPendingOperationsToFlush
= 0;
549 void SkiaSalGraphicsImpl::setCanvasScalingAndClipping()
551 SkCanvas
* canvas
= mSurface
->getCanvas();
552 assert(canvas
->getSaveCount() == 1);
553 // If HiDPI scaling is active, simply set a scaling matrix for the canvas. This means
554 // that all painting can use VCL coordinates and they'll be automatically translated to mSurface
555 // scaled coordinates. If that is not wanted, the scale() state needs to be temporarily unset.
556 // State such as mDirtyRect and mXorRegion is not scaled, the scaling matrix applies to clipping too,
557 // and the rest needs to be handled explicitly.
558 // When reading mSurface contents there's no automatic scaling and it needs to be handled explicitly.
559 canvas
->save(); // keep the original state without any scaling
560 canvas
->scale(mScaling
, mScaling
);
562 // SkCanvas::clipRegion() can only further reduce the clip region,
563 // but we need to set the given region, which may extend it.
564 // So handle that by always having the full clip region saved on the stack
565 // and always go back to that. SkCanvas::restore() only affects the clip
567 canvas
->save(); // keep scaled state without clipping
568 setCanvasClipRegion(canvas
, mClipRegion
);
571 void SkiaSalGraphicsImpl::resetCanvasScalingAndClipping()
573 SkCanvas
* canvas
= mSurface
->getCanvas();
574 assert(canvas
->getSaveCount() == 3);
575 canvas
->restore(); // undo clipping
576 canvas
->restore(); // undo scaling
579 bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region
& region
)
581 if (mClipRegion
== region
)
584 checkPendingDrawing();
586 mClipRegion
= region
;
587 SAL_INFO("vcl.skia.trace", "setclipregion(" << this << "): " << region
);
588 SkCanvas
* canvas
= mSurface
->getCanvas();
589 assert(canvas
->getSaveCount() == 3);
590 canvas
->restore(); // undo previous clip state, see setCanvasScalingAndClipping()
592 setCanvasClipRegion(canvas
, region
);
596 void SkiaSalGraphicsImpl::setCanvasClipRegion(SkCanvas
* canvas
, const vcl::Region
& region
)
600 // Always use region rectangles, regardless of what the region uses internally.
601 // That's what other VCL backends do, and trying to use addPolyPolygonToPath()
602 // in case a polygon is used leads to off-by-one errors such as tdf#133208.
603 RectangleVector rectangles
;
604 region
.GetRegionRectangles(rectangles
);
605 path
.incReserve(rectangles
.size() + 1);
606 for (const tools::Rectangle
& rectangle
: rectangles
)
607 path
.addRect(SkRect::MakeXYWH(rectangle
.getX(), rectangle
.getY(), rectangle
.GetWidth(),
608 rectangle
.GetHeight()));
609 path
.setFillType(SkPathFillType::kEvenOdd
);
610 canvas
->clipPath(path
);
613 void SkiaSalGraphicsImpl::ResetClipRegion()
615 setClipRegion(vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight())));
618 const vcl::Region
& SkiaSalGraphicsImpl::getClipRegion() const { return mClipRegion
; }
620 sal_uInt16
SkiaSalGraphicsImpl::GetBitCount() const { return 32; }
622 tools::Long
SkiaSalGraphicsImpl::GetGraphicsWidth() const { return GetWidth(); }
624 void SkiaSalGraphicsImpl::SetLineColor()
626 checkPendingDrawing();
627 mLineColor
= SALCOLOR_NONE
;
630 void SkiaSalGraphicsImpl::SetLineColor(Color nColor
)
632 checkPendingDrawing();
636 void SkiaSalGraphicsImpl::SetFillColor()
638 checkPendingDrawing();
639 mFillColor
= SALCOLOR_NONE
;
642 void SkiaSalGraphicsImpl::SetFillColor(Color nColor
)
644 checkPendingDrawing();
648 void SkiaSalGraphicsImpl::SetXORMode(bool set
, bool)
652 checkPendingDrawing();
653 SAL_INFO("vcl.skia.trace", "setxormode(" << this << "): " << set
);
655 mXorRegion
.setEmpty();
661 SkCanvas
* SkiaSalGraphicsImpl::getXorCanvas()
665 // Skia does not implement xor drawing, so we need to handle it manually by redirecting
666 // to a temporary SkBitmap and then doing the xor operation on the data ourselves.
667 // There's no point in using SkSurface for GPU, we'd immediately need to get the pixels back.
670 // Use unpremultiplied alpha (see xor applying in applyXor()).
671 if (!mXorBitmap
.tryAllocPixels(mSurface
->imageInfo().makeAlphaType(kUnpremul_SkAlphaType
)))
673 mXorBitmap
.eraseARGB(0, 0, 0, 0);
674 mXorCanvas
= std::make_unique
<SkCanvas
>(mXorBitmap
);
676 mXorCanvas
->scale(mScaling
, mScaling
);
677 setCanvasClipRegion(mXorCanvas
.get(), mClipRegion
);
679 return mXorCanvas
.get();
682 void SkiaSalGraphicsImpl::applyXor()
684 // Apply the result from the temporary bitmap manually. This is indeed
685 // slow, but it doesn't seem to be needed often and is optimized
686 // in each operation by extending mXorRegion with the area that should be
689 if (mScaling
!= 1 && !mXorRegion
.isEmpty())
691 // Scale mXorRegion to mSurface coordinates if needed.
692 std::vector
<SkIRect
> rects
;
693 for (SkRegion::Iterator
it(mXorRegion
); !it
.done(); it
.next())
694 rects
.push_back(scaleRect(it
.rect(), mScaling
));
695 mXorRegion
.setRects(rects
.data(), rects
.size());
697 if (!mSurface
|| !mXorCanvas
698 || !mXorRegion
.op(SkIRect::MakeXYWH(0, 0, mSurface
->width(), mSurface
->height()),
699 SkRegion::kIntersect_Op
))
701 mXorRegion
.setEmpty();
704 SAL_INFO("vcl.skia.trace", "applyxor(" << this << "): " << mXorRegion
);
705 // Copy the surface contents to another pixmap.
706 SkBitmap surfaceBitmap
;
707 // Use unpremultiplied alpha format, so that we do not have to do the conversions to get
708 // the RGB and back (Skia will do it when converting, but it'll be presumably faster at it).
709 if (!surfaceBitmap
.tryAllocPixels(mSurface
->imageInfo().makeAlphaType(kUnpremul_SkAlphaType
)))
712 paint
.setBlendMode(SkBlendMode::kSrc
); // copy as is
713 SkRect area
= SkRect::Make(mXorRegion
.getBounds());
715 SkCanvas
canvas(surfaceBitmap
);
716 canvas
.drawImageRect(makeCheckedImageSnapshot(mSurface
), area
, area
, SkSamplingOptions(),
717 &paint
, SkCanvas::kFast_SrcRectConstraint
);
719 // xor to surfaceBitmap
720 assert(surfaceBitmap
.info().alphaType() == kUnpremul_SkAlphaType
);
721 assert(mXorBitmap
.info().alphaType() == kUnpremul_SkAlphaType
);
722 assert(surfaceBitmap
.bytesPerPixel() == 4);
723 assert(mXorBitmap
.bytesPerPixel() == 4);
724 for (SkRegion::Iterator
it(mXorRegion
); !it
.done(); it
.next())
726 for (int y
= it
.rect().top(); y
< it
.rect().bottom(); ++y
)
728 uint8_t* data
= static_cast<uint8_t*>(surfaceBitmap
.getAddr(it
.rect().x(), y
));
729 const uint8_t* xordata
= static_cast<uint8_t*>(mXorBitmap
.getAddr(it
.rect().x(), y
));
730 for (int x
= 0; x
< it
.rect().width(); ++x
)
732 *data
++ ^= *xordata
++;
733 *data
++ ^= *xordata
++;
734 *data
++ ^= *xordata
++;
735 // alpha is not xor-ed
741 surfaceBitmap
.notifyPixelsChanged();
742 surfaceBitmap
.setImmutable();
743 // Copy without any clipping or scaling.
744 resetCanvasScalingAndClipping();
745 mSurface
->getCanvas()->drawImageRect(surfaceBitmap
.asImage(), area
, area
, SkSamplingOptions(),
746 &paint
, SkCanvas::kFast_SrcRectConstraint
);
747 setCanvasScalingAndClipping();
750 mXorRegion
.setEmpty();
753 void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor
)
755 checkPendingDrawing();
758 case SalROPColor::N0
:
759 mLineColor
= Color(0, 0, 0);
761 case SalROPColor::N1
:
762 mLineColor
= Color(0xff, 0xff, 0xff);
764 case SalROPColor::Invert
:
765 mLineColor
= Color(0xff, 0xff, 0xff);
770 void SkiaSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor
)
772 checkPendingDrawing();
775 case SalROPColor::N0
:
776 mFillColor
= Color(0, 0, 0);
778 case SalROPColor::N1
:
779 mFillColor
= Color(0xff, 0xff, 0xff);
781 case SalROPColor::Invert
:
782 mFillColor
= Color(0xff, 0xff, 0xff);
787 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX
, tools::Long nY
)
789 drawPixel(nX
, nY
, mLineColor
);
792 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX
, tools::Long nY
, Color nColor
)
794 if (nColor
== SALCOLOR_NONE
)
797 SAL_INFO("vcl.skia.trace", "drawpixel(" << this << "): " << Point(nX
, nY
) << ":" << nColor
);
798 addUpdateRegion(SkRect::MakeXYWH(nX
, nY
, 1, 1));
800 paint
.setColor(toSkColor(nColor
));
801 // Apparently drawPixel() is actually expected to set the pixel and not draw it.
802 paint
.setBlendMode(SkBlendMode::kSrc
); // set as is, including alpha
803 if (mScaling
!= 1 && isUnitTestRunning())
805 // On HiDPI displays, draw a square on the entire non-hidpi "pixel" when running unittests,
806 // since tests often require precise pixel drawing.
807 paint
.setStrokeWidth(1); // this will be scaled by mScaling
808 paint
.setStrokeCap(SkPaint::kSquare_Cap
);
810 getDrawCanvas()->drawPoint(toSkX(nX
), toSkY(nY
), paint
);
814 void SkiaSalGraphicsImpl::drawLine(tools::Long nX1
, tools::Long nY1
, tools::Long nX2
,
817 if (mLineColor
== SALCOLOR_NONE
)
820 SAL_INFO("vcl.skia.trace", "drawline(" << this << "): " << Point(nX1
, nY1
) << "->"
821 << Point(nX2
, nY2
) << ":" << mLineColor
);
822 addUpdateRegion(SkRect::MakeLTRB(nX1
, nY1
, nX2
, nY2
).makeSorted());
823 SkPaint paint
= makeLinePaint();
824 paint
.setAntiAlias(mParent
.getAntiAlias());
825 if (mScaling
!= 1 && isUnitTestRunning())
827 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
828 // smoothing that would confuse unittests.
829 paint
.setStrokeWidth(1); // this will be scaled by mScaling
830 paint
.setStrokeCap(SkPaint::kSquare_Cap
);
832 getDrawCanvas()->drawLine(toSkX(nX1
), toSkY(nY1
), toSkX(nX2
), toSkY(nY2
), paint
);
836 void SkiaSalGraphicsImpl::privateDrawAlphaRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
837 tools::Long nHeight
, double fTransparency
,
841 SAL_INFO("vcl.skia.trace",
842 "privatedrawrect(" << this << "): " << SkIRect::MakeXYWH(nX
, nY
, nWidth
, nHeight
)
843 << ":" << mLineColor
<< ":" << mFillColor
<< ":" << fTransparency
);
844 addUpdateRegion(SkRect::MakeXYWH(nX
, nY
, nWidth
, nHeight
));
845 SkCanvas
* canvas
= getDrawCanvas();
846 if (mFillColor
!= SALCOLOR_NONE
)
848 SkPaint paint
= makeFillPaint(fTransparency
);
849 paint
.setAntiAlias(!blockAA
&& mParent
.getAntiAlias());
850 // HACK: If the polygon is just a line, it still should be drawn. But when filling
851 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
852 if (mLineColor
== SALCOLOR_NONE
&& SkSize::Make(nWidth
, nHeight
).isEmpty())
853 paint
.setStyle(SkPaint::kStroke_Style
);
854 canvas
->drawIRect(SkIRect::MakeXYWH(nX
, nY
, nWidth
, nHeight
), paint
);
856 if (mLineColor
!= SALCOLOR_NONE
&& mLineColor
!= mFillColor
) // otherwise handled by fill
858 SkPaint paint
= makeLinePaint(fTransparency
);
859 paint
.setAntiAlias(!blockAA
&& mParent
.getAntiAlias());
860 if (mScaling
!= 1 && isUnitTestRunning())
862 // On HiDPI displays, do not draw just a hairline but instead a full-width "pixel" when running unittests,
863 // since tests often require precise pixel drawing.
864 paint
.setStrokeWidth(1); // this will be scaled by mScaling
865 paint
.setStrokeCap(SkPaint::kSquare_Cap
);
867 // The obnoxious "-1 DrawRect()" hack that I don't understand the purpose of (and I'm not sure
868 // if anybody does), but without it some cases do not work. The max() is needed because Skia
869 // will not draw anything if width or height is 0.
870 canvas
->drawRect(SkRect::MakeXYWH(toSkX(nX
), toSkY(nY
),
871 std::max(tools::Long(1), nWidth
- 1),
872 std::max(tools::Long(1), nHeight
- 1)),
878 void SkiaSalGraphicsImpl::drawRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
881 privateDrawAlphaRect(nX
, nY
, nWidth
, nHeight
, 0.0, true);
884 void SkiaSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints
, const Point
* pPtAry
)
886 basegfx::B2DPolygon aPolygon
;
887 aPolygon
.append(basegfx::B2DPoint(pPtAry
->getX(), pPtAry
->getY()), nPoints
);
888 for (sal_uInt32 i
= 1; i
< nPoints
; ++i
)
889 aPolygon
.setB2DPoint(i
, basegfx::B2DPoint(pPtAry
[i
].getX(), pPtAry
[i
].getY()));
890 aPolygon
.setClosed(false);
892 drawPolyLine(basegfx::B2DHomMatrix(), aPolygon
, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter
,
893 css::drawing::LineCap_BUTT
, basegfx::deg2rad(15.0) /*default*/, false);
896 void SkiaSalGraphicsImpl::drawPolygon(sal_uInt32 nPoints
, const Point
* pPtAry
)
898 basegfx::B2DPolygon aPolygon
;
899 aPolygon
.append(basegfx::B2DPoint(pPtAry
->getX(), pPtAry
->getY()), nPoints
);
900 for (sal_uInt32 i
= 1; i
< nPoints
; ++i
)
901 aPolygon
.setB2DPoint(i
, basegfx::B2DPoint(pPtAry
[i
].getX(), pPtAry
[i
].getY()));
903 drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPolygon
), 0.0);
906 void SkiaSalGraphicsImpl::drawPolyPolygon(sal_uInt32 nPoly
, const sal_uInt32
* pPoints
,
907 const Point
** pPtAry
)
909 basegfx::B2DPolyPolygon aPolyPolygon
;
910 for (sal_uInt32 nPolygon
= 0; nPolygon
< nPoly
; ++nPolygon
)
912 sal_uInt32 nPoints
= pPoints
[nPolygon
];
915 const Point
* pSubPoints
= pPtAry
[nPolygon
];
916 basegfx::B2DPolygon aPolygon
;
917 aPolygon
.append(basegfx::B2DPoint(pSubPoints
->getX(), pSubPoints
->getY()), nPoints
);
918 for (sal_uInt32 i
= 1; i
< nPoints
; ++i
)
919 aPolygon
.setB2DPoint(i
,
920 basegfx::B2DPoint(pSubPoints
[i
].getX(), pSubPoints
[i
].getY()));
922 aPolyPolygon
.append(aPolygon
);
926 drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon
, 0.0);
929 bool SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix
& rObjectToDevice
,
930 const basegfx::B2DPolyPolygon
& rPolyPolygon
,
931 double fTransparency
)
933 const bool bHasFill(mFillColor
!= SALCOLOR_NONE
);
934 const bool bHasLine(mLineColor
!= SALCOLOR_NONE
);
936 if (rPolyPolygon
.count() == 0 || !(bHasFill
|| bHasLine
) || fTransparency
< 0.0
937 || fTransparency
>= 1.0)
940 basegfx::B2DPolyPolygon
aPolyPolygon(rPolyPolygon
);
941 aPolyPolygon
.transform(rObjectToDevice
);
943 SAL_INFO("vcl.skia.trace", "drawpolypolygon(" << this << "): " << aPolyPolygon
<< ":"
944 << mLineColor
<< ":" << mFillColor
);
946 if (delayDrawPolyPolygon(aPolyPolygon
, fTransparency
))
952 performDrawPolyPolygon(aPolyPolygon
, fTransparency
, mParent
.getAntiAlias());
956 void SkiaSalGraphicsImpl::performDrawPolyPolygon(const basegfx::B2DPolyPolygon
& aPolyPolygon
,
957 double fTransparency
, bool useAA
)
962 bool hasOnlyOrthogonal
= true;
963 addPolyPolygonToPath(aPolyPolygon
, polygonPath
, &hasOnlyOrthogonal
);
964 polygonPath
.setFillType(SkPathFillType::kEvenOdd
);
965 addUpdateRegion(polygonPath
.getBounds());
967 // For lines we use toSkX()/toSkY() in order to pass centers of pixels to Skia,
968 // as that leads to better results with floating-point coordinates
969 // (e.g. https://bugs.chromium.org/p/skia/issues/detail?id=9611).
970 // But that means that we generally need to use it also for areas, so that they
971 // line up properly if used together (tdf#134346).
972 // On the other hand, with AA enabled and rectangular areas, this leads to fuzzy
973 // edges (tdf#137329). But since rectangular areas line up perfectly to pixels
974 // everywhere, it shouldn't be necessary to do this for them.
975 // So if AA is enabled, avoid this fixup for rectangular areas.
976 if (!useAA
|| !hasOnlyOrthogonal
)
978 // We normally use pixel at their center positions, but slightly off (see toSkX/Y()).
979 // With AA lines that "slightly off" causes tiny changes of color, making some tests
980 // fail. Since moving AA-ed line slightly to a side doesn't cause any real visual
981 // difference, just place exactly at the center. tdf#134346
982 const SkScalar posFix
= useAA
? toSkXYFix
: 0;
983 polygonPath
.offset(toSkX(0) + posFix
, toSkY(0) + posFix
, nullptr);
985 if (mFillColor
!= SALCOLOR_NONE
)
987 SkPaint aPaint
= makeFillPaint(fTransparency
);
988 aPaint
.setAntiAlias(useAA
);
989 // HACK: If the polygon is just a line, it still should be drawn. But when filling
990 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
991 if (mLineColor
== SALCOLOR_NONE
&& polygonPath
.getBounds().isEmpty())
992 aPaint
.setStyle(SkPaint::kStroke_Style
);
993 getDrawCanvas()->drawPath(polygonPath
, aPaint
);
995 if (mLineColor
!= SALCOLOR_NONE
&& mLineColor
!= mFillColor
) // otherwise handled by fill
997 SkPaint aPaint
= makeLinePaint(fTransparency
);
998 aPaint
.setAntiAlias(useAA
);
999 getDrawCanvas()->drawPath(polygonPath
, aPaint
);
1003 // WORKAROUND: The logo in the about dialog has drawing errors. This seems to happen
1004 // only on Linux (not Windows on the same machine), with both AMDGPU and Mesa,
1005 // and only when antialiasing is enabled. Flushing seems to avoid the problem.
1006 if (useAA
&& getVendor() == DriverBlocklist::VendorAMD
)
1007 mSurface
->flushAndSubmit();
1015 bool operator()(const basegfx::B2DPoint
& point1
, const basegfx::B2DPoint
& point2
) const
1017 if (basegfx::fTools::equal(point1
.getX(), point2
.getX()))
1018 return basegfx::fTools::less(point1
.getY(), point2
.getY());
1019 return basegfx::fTools::less(point1
.getX(), point2
.getX());
1024 bool SkiaSalGraphicsImpl::delayDrawPolyPolygon(const basegfx::B2DPolyPolygon
& aPolyPolygon
,
1025 double fTransparency
)
1027 // There is some code that needlessly subdivides areas into adjacent rectangles,
1028 // but Skia doesn't line them up perfectly if AA is enabled (e.g. Cairo, Qt5 do,
1029 // but Skia devs claim it's working as intended
1030 // https://groups.google.com/d/msg/skia-discuss/NlKpD2X_5uc/Vuwd-kyYBwAJ).
1031 // An example is tdf#133016, which triggers SvgStyleAttributes::add_stroke()
1032 // implementing a line stroke as a bunch of polygons instead of just one, and
1033 // SvgLinearAtomPrimitive2D::create2DDecomposition() creates a gradient
1034 // as a series of polygons of gradually changing color. Those places should be
1035 // changed, but try to merge those split polygons back into the original one,
1036 // where the needlessly created edges causing problems will not exist.
1037 // This means drawing of such polygons needs to be delayed, so that they can
1038 // be possibly merged with the next one.
1039 // Merge only polygons of the same properties (color, etc.), so the gradient problem
1040 // actually isn't handled here.
1042 // Only AA polygons need merging, because they do not line up well because of the AA of the edges.
1043 if (!mParent
.getAntiAlias())
1045 // Only filled polygons without an outline are problematic.
1046 if (mFillColor
== SALCOLOR_NONE
|| mLineColor
!= SALCOLOR_NONE
)
1048 // Merge only simple polygons, real polypolygons most likely aren't needlessly split,
1049 // so they do not need joining.
1050 if (aPolyPolygon
.count() != 1)
1052 // If the polygon is not closed, it doesn't mark an area to be filled.
1053 if (!aPolyPolygon
.isClosed())
1055 // If a polygon does not contain a straight line, i.e. it's all curves, then do not merge.
1056 // First of all that's even more expensive, and second it's very unlikely that it's a polygon
1057 // split into more polygons.
1058 if (!polygonContainsLine(aPolyPolygon
))
1061 if (mLastPolyPolygonInfo
.polygons
.size() != 0
1062 && (mLastPolyPolygonInfo
.transparency
!= fTransparency
1063 || !mLastPolyPolygonInfo
.bounds
.overlaps(aPolyPolygon
.getB2DRange())))
1065 checkPendingDrawing(); // Cannot be parts of the same larger polygon, draw the last and reset.
1067 if (!mLastPolyPolygonInfo
.polygons
.empty())
1069 assert(aPolyPolygon
.count() == 1);
1070 assert(mLastPolyPolygonInfo
.polygons
.back().count() == 1);
1071 // Check if the new and the previous polygon share at least one point. If not, then they
1072 // cannot be adjacent polygons, so there's no point in trying to merge them.
1073 bool sharePoint
= false;
1074 const basegfx::B2DPolygon
& poly1
= aPolyPolygon
.getB2DPolygon(0);
1075 const basegfx::B2DPolygon
& poly2
= mLastPolyPolygonInfo
.polygons
.back().getB2DPolygon(0);
1076 o3tl::sorted_vector
<basegfx::B2DPoint
, LessThan
> poly1Points
; // for O(n log n)
1077 poly1Points
.reserve(poly1
.count());
1078 for (sal_uInt32 i
= 0; i
< poly1
.count(); ++i
)
1079 poly1Points
.insert(poly1
.getB2DPoint(i
));
1080 for (sal_uInt32 i
= 0; i
< poly2
.count(); ++i
)
1081 if (poly1Points
.find(poly2
.getB2DPoint(i
)) != poly1Points
.end())
1087 checkPendingDrawing(); // Draw the previous one and reset.
1089 // Collect the polygons that can be possibly merged. Do the merging only once at the end,
1090 // because it's not a cheap operation.
1091 mLastPolyPolygonInfo
.polygons
.push_back(aPolyPolygon
);
1092 mLastPolyPolygonInfo
.bounds
.expand(aPolyPolygon
.getB2DRange());
1093 mLastPolyPolygonInfo
.transparency
= fTransparency
;
1097 // Tdf#140848 - basegfx::utils::mergeToSinglePolyPolygon() seems to have rounding
1098 // errors that sometimes cause it to merge incorrectly.
1099 static void roundPolygonPoints(basegfx::B2DPolyPolygon
& polyPolygon
)
1101 for (basegfx::B2DPolygon
& polygon
: polyPolygon
)
1103 polygon
.makeUnique();
1104 for (sal_uInt32 i
= 0; i
< polygon
.count(); ++i
)
1105 polygon
.setB2DPoint(i
, basegfx::B2DPoint(basegfx::fround(polygon
.getB2DPoint(i
))));
1106 // Control points are saved as vectors relative to points, so hopefully
1107 // there's no need to round those.
1111 void SkiaSalGraphicsImpl::checkPendingDrawing()
1113 if (mLastPolyPolygonInfo
.polygons
.size() != 0)
1114 { // Flush any pending polygon drawing.
1115 basegfx::B2DPolyPolygonVector polygons
;
1116 std::swap(polygons
, mLastPolyPolygonInfo
.polygons
);
1117 double transparency
= mLastPolyPolygonInfo
.transparency
;
1118 mLastPolyPolygonInfo
.bounds
.reset();
1119 if (polygons
.size() == 1)
1120 performDrawPolyPolygon(polygons
.front(), transparency
, true);
1123 for (basegfx::B2DPolyPolygon
& p
: polygons
)
1124 roundPolygonPoints(p
);
1125 performDrawPolyPolygon(basegfx::utils::mergeToSinglePolyPolygon(polygons
), transparency
,
1131 bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix
& rObjectToDevice
,
1132 const basegfx::B2DPolygon
& rPolyLine
, double fTransparency
,
1133 double fLineWidth
, const std::vector
<double>* pStroke
,
1134 basegfx::B2DLineJoin eLineJoin
,
1135 css::drawing::LineCap eLineCap
, double fMiterMinimumAngle
,
1136 bool bPixelSnapHairline
)
1138 if (!rPolyLine
.count() || fTransparency
< 0.0 || fTransparency
> 1.0
1139 || mLineColor
== SALCOLOR_NONE
)
1145 SAL_INFO("vcl.skia.trace", "drawpolyline(" << this << "): " << rPolyLine
<< ":" << mLineColor
);
1147 // Adjust line width for object-to-device scale.
1148 fLineWidth
= (rObjectToDevice
* basegfx::B2DVector(fLineWidth
, 0)).getLength();
1149 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
1150 // smoothing that would confuse unittests.
1151 if (fLineWidth
== 0 && mScaling
!= 1 && isUnitTestRunning())
1152 fLineWidth
= 1; // this will be scaled by mScaling
1154 // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
1155 basegfx::B2DPolygon
aPolyLine(rPolyLine
);
1156 aPolyLine
.transform(rObjectToDevice
);
1157 if (bPixelSnapHairline
)
1159 aPolyLine
= basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine
);
1162 SkPaint aPaint
= makeLinePaint(fTransparency
);
1166 case basegfx::B2DLineJoin::Bevel
:
1167 aPaint
.setStrokeJoin(SkPaint::kBevel_Join
);
1169 case basegfx::B2DLineJoin::Round
:
1170 aPaint
.setStrokeJoin(SkPaint::kRound_Join
);
1172 case basegfx::B2DLineJoin::NONE
:
1174 case basegfx::B2DLineJoin::Miter
:
1175 aPaint
.setStrokeJoin(SkPaint::kMiter_Join
);
1176 // convert miter minimum angle to miter limit
1177 aPaint
.setStrokeMiter(1.0 / std::sin(fMiterMinimumAngle
/ 2.0));
1183 case css::drawing::LineCap_ROUND
:
1184 aPaint
.setStrokeCap(SkPaint::kRound_Cap
);
1186 case css::drawing::LineCap_SQUARE
:
1187 aPaint
.setStrokeCap(SkPaint::kSquare_Cap
);
1189 default: // css::drawing::LineCap_BUTT:
1190 aPaint
.setStrokeCap(SkPaint::kButt_Cap
);
1194 aPaint
.setStrokeWidth(fLineWidth
);
1195 aPaint
.setAntiAlias(mParent
.getAntiAlias());
1196 // See the tdf#134346 comment above.
1197 const SkScalar posFix
= mParent
.getAntiAlias() ? toSkXYFix
: 0;
1199 if (pStroke
&& std::accumulate(pStroke
->begin(), pStroke
->end(), 0.0) != 0)
1201 std::vector
<SkScalar
> intervals
;
1202 // Transform size by the matrix.
1203 for (double stroke
: *pStroke
)
1204 intervals
.push_back((rObjectToDevice
* basegfx::B2DVector(stroke
, 0)).getLength());
1205 aPaint
.setPathEffect(SkDashPathEffect::Make(intervals
.data(), intervals
.size(), 0));
1208 // Skia does not support basegfx::B2DLineJoin::NONE, so in that case batch only if lines
1209 // are not wider than a pixel.
1210 if (eLineJoin
!= basegfx::B2DLineJoin::NONE
|| fLineWidth
<= 1.0)
1213 aPath
.incReserve(aPolyLine
.count() * 3); // because cubicTo is 3 elements
1214 addPolygonToPath(aPolyLine
, aPath
);
1215 aPath
.offset(toSkX(0) + posFix
, toSkY(0) + posFix
, nullptr);
1216 addUpdateRegion(aPath
.getBounds());
1217 getDrawCanvas()->drawPath(aPath
, aPaint
);
1221 sal_uInt32 nPoints
= aPolyLine
.count();
1222 bool bClosed
= aPolyLine
.isClosed();
1223 bool bHasCurves
= aPolyLine
.areControlPointsUsed();
1224 for (sal_uInt32 j
= 0; j
< nPoints
; ++j
)
1227 aPath
.incReserve(2 * 3); // because cubicTo is 3 elements
1228 addPolygonToPath(aPolyLine
, aPath
, j
, j
+ 1, nPoints
, bClosed
, bHasCurves
);
1229 aPath
.offset(toSkX(0) + posFix
, toSkY(0) + posFix
, nullptr);
1230 addUpdateRegion(aPath
.getBounds());
1231 getDrawCanvas()->drawPath(aPath
, aPaint
);
1240 bool SkiaSalGraphicsImpl::drawPolyLineBezier(sal_uInt32
, const Point
*, const PolyFlags
*)
1245 bool SkiaSalGraphicsImpl::drawPolygonBezier(sal_uInt32
, const Point
*, const PolyFlags
*)
1250 bool SkiaSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32
, const sal_uInt32
*, const Point
* const*,
1251 const PolyFlags
* const*)
1256 void SkiaSalGraphicsImpl::copyArea(tools::Long nDestX
, tools::Long nDestY
, tools::Long nSrcX
,
1257 tools::Long nSrcY
, tools::Long nSrcWidth
, tools::Long nSrcHeight
,
1258 bool /*bWindowInvalidate*/)
1260 if (nDestX
== nSrcX
&& nDestY
== nSrcY
)
1263 SAL_INFO("vcl.skia.trace", "copyarea("
1264 << this << "): " << Point(nSrcX
, nSrcY
) << "->"
1265 << SkIRect::MakeXYWH(nDestX
, nDestY
, nSrcWidth
, nSrcHeight
));
1266 // Using SkSurface::draw() should be more efficient, but it's too buggy.
1267 SalTwoRect
rPosAry(nSrcX
, nSrcY
, nSrcWidth
, nSrcHeight
, nDestX
, nDestY
, nSrcWidth
, nSrcHeight
);
1268 privateCopyBits(rPosAry
, this);
1272 void SkiaSalGraphicsImpl::copyBits(const SalTwoRect
& rPosAry
, SalGraphics
* pSrcGraphics
)
1275 SkiaSalGraphicsImpl
* src
;
1278 assert(dynamic_cast<SkiaSalGraphicsImpl
*>(pSrcGraphics
->GetImpl()));
1279 src
= static_cast<SkiaSalGraphicsImpl
*>(pSrcGraphics
->GetImpl());
1280 src
->checkSurface();
1281 src
->flushDrawing();
1288 auto srcDebug
= [&]() -> std::string
{
1293 std::ostringstream stream
;
1294 stream
<< "(" << src
<< ")";
1295 return stream
.str();
1298 SAL_INFO("vcl.skia.trace", "copybits(" << this << "): " << srcDebug() << ": " << rPosAry
);
1299 privateCopyBits(rPosAry
, src
);
1303 void SkiaSalGraphicsImpl::privateCopyBits(const SalTwoRect
& rPosAry
, SkiaSalGraphicsImpl
* src
)
1306 addUpdateRegion(SkRect::MakeXYWH(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
,
1307 rPosAry
.mnDestHeight
));
1309 paint
.setBlendMode(SkBlendMode::kSrc
); // copy as is, including alpha
1310 SkIRect srcRect
= SkIRect::MakeXYWH(rPosAry
.mnSrcX
, rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
,
1311 rPosAry
.mnSrcHeight
);
1312 SkRect destRect
= SkRect::MakeXYWH(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
,
1313 rPosAry
.mnDestHeight
);
1315 if (!SkIRect::Intersects(srcRect
, SkIRect::MakeWH(src
->GetWidth(), src
->GetHeight()))
1316 || !SkRect::Intersects(destRect
, SkRect::MakeWH(GetWidth(), GetHeight())))
1321 // Copy-to-self means that we'd take a snapshot, which would refcount the data,
1322 // and then drawing would result in copy in write, copying the entire surface.
1323 // Try to copy less by making a snapshot of only what is needed.
1324 // A complication here is that drawImageRect() can handle coordinates outside
1325 // of surface fine, but makeImageSnapshot() will crop to the surface area,
1326 // so do that manually here in order to adjust also destination rectangle.
1327 if (srcRect
.x() < 0 || srcRect
.y() < 0)
1329 destRect
.fLeft
+= -srcRect
.x();
1330 destRect
.fTop
+= -srcRect
.y();
1331 srcRect
.adjust(-srcRect
.x(), -srcRect
.y(), 0, 0);
1333 // Note that right() and bottom() are not inclusive (are outside of the rect).
1334 if (srcRect
.right() - 1 > GetWidth() || srcRect
.bottom() - 1 > GetHeight())
1336 destRect
.fRight
+= GetWidth() - srcRect
.right();
1337 destRect
.fBottom
+= GetHeight() - srcRect
.bottom();
1338 srcRect
.adjust(0, 0, GetWidth() - srcRect
.right(), GetHeight() - srcRect
.bottom());
1340 // Scaling for source coordinates must be done manually.
1341 if (src
->mScaling
!= 1)
1342 srcRect
= scaleRect(srcRect
, src
->mScaling
);
1343 sk_sp
<SkImage
> image
= makeCheckedImageSnapshot(src
->mSurface
, srcRect
);
1344 srcRect
.offset(-srcRect
.x(), -srcRect
.y());
1345 getDrawCanvas()->drawImageRect(image
, SkRect::Make(srcRect
), destRect
,
1346 makeSamplingOptions(rPosAry
, mScaling
, src
->mScaling
),
1347 &paint
, SkCanvas::kFast_SrcRectConstraint
);
1351 // Scaling for source coordinates must be done manually.
1352 if (src
->mScaling
!= 1)
1353 srcRect
= scaleRect(srcRect
, src
->mScaling
);
1354 // Do not use makeImageSnapshot(rect), as that one may make a needless data copy.
1355 getDrawCanvas()->drawImageRect(makeCheckedImageSnapshot(src
->mSurface
),
1356 SkRect::Make(srcRect
), destRect
,
1357 makeSamplingOptions(rPosAry
, mScaling
, src
->mScaling
),
1358 &paint
, SkCanvas::kFast_SrcRectConstraint
);
1362 bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rBitmap
)
1364 if (checkInvalidSourceOrDestination(rPosAry
))
1367 assert(dynamic_cast<const SkiaSalBitmap
*>(&rBitmap
));
1368 const SkiaSalBitmap
& rSkiaBitmap
= static_cast<const SkiaSalBitmap
&>(rBitmap
);
1369 // This is used by VirtualDevice in the alpha mode for the "alpha" layer which
1370 // is actually one-minus-alpha (opacity). Therefore white=0xff=transparent,
1371 // black=0x00=opaque. So the result is transparent only if both the inputs
1372 // are transparent. Since for blending operations white=1.0 and black=0.0,
1373 // kMultiply should handle exactly that (transparent*transparent=transparent,
1374 // opaque*transparent=opaque). And guessing from the "floor" in TYPE_BLEND in opengl's
1375 // combinedTextureFragmentShader.glsl, the layer is not even alpha values but
1376 // simply yes-or-no mask.
1377 // See also blendAlphaBitmap().
1378 if (rSkiaBitmap
.IsFullyOpaqueAsAlpha())
1380 // Optimization. If the bitmap means fully opaque, it's all zero's. In CPU
1381 // mode it should be faster to just copy instead of SkBlendMode::kMultiply.
1382 drawBitmap(rPosAry
, rSkiaBitmap
);
1385 drawBitmap(rPosAry
, rSkiaBitmap
, SkBlendMode::kMultiply
);
1389 bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect
& rPosAry
,
1390 const SalBitmap
& rSourceBitmap
,
1391 const SalBitmap
& rMaskBitmap
,
1392 const SalBitmap
& rAlphaBitmap
)
1394 if (checkInvalidSourceOrDestination(rPosAry
))
1397 assert(dynamic_cast<const SkiaSalBitmap
*>(&rSourceBitmap
));
1398 assert(dynamic_cast<const SkiaSalBitmap
*>(&rMaskBitmap
));
1399 assert(dynamic_cast<const SkiaSalBitmap
*>(&rAlphaBitmap
));
1400 const SkiaSalBitmap
& rSkiaSourceBitmap
= static_cast<const SkiaSalBitmap
&>(rSourceBitmap
);
1401 const SkiaSalBitmap
& rSkiaMaskBitmap
= static_cast<const SkiaSalBitmap
&>(rMaskBitmap
);
1402 const SkiaSalBitmap
& rSkiaAlphaBitmap
= static_cast<const SkiaSalBitmap
&>(rAlphaBitmap
);
1404 if (rSkiaMaskBitmap
.IsFullyOpaqueAsAlpha())
1406 // Optimization. If the mask of the bitmap to be blended means it's actually opaque,
1407 // just draw the bitmap directly (that's what the math below will result in).
1408 drawBitmap(rPosAry
, rSkiaSourceBitmap
);
1411 // This was originally implemented for the OpenGL drawing method and it is poorly documented.
1412 // The source and mask bitmaps are the usual data and alpha bitmaps, and 'alpha'
1413 // is the "alpha" layer of the VirtualDevice (the alpha in VirtualDevice is also stored
1414 // as a separate bitmap). Now if I understand it correctly these two alpha masks first need
1415 // to be combined into the actual alpha mask to be used. The formula for TYPE_BLEND
1416 // in opengl's combinedTextureFragmentShader.glsl is
1417 // "result_alpha = 1.0 - (1.0 - floor(alpha)) * mask".
1418 // See also blendBitmap().
1420 SkSamplingOptions samplingOptions
= makeSamplingOptions(rPosAry
, mScaling
);
1421 // First do the "( 1 - alpha ) * mask"
1422 // (no idea how to do "floor", but hopefully not needed in practice).
1423 sk_sp
<SkShader
> shaderAlpha
1424 = SkShaders::Blend(SkBlendMode::kDstOut
, rSkiaMaskBitmap
.GetAlphaSkShader(samplingOptions
),
1425 rSkiaAlphaBitmap
.GetAlphaSkShader(samplingOptions
));
1426 // And now draw the bitmap with "1 - x", where x is the "( 1 - alpha ) * mask".
1427 sk_sp
<SkShader
> shader
= SkShaders::Blend(SkBlendMode::kSrcOut
, shaderAlpha
,
1428 rSkiaSourceBitmap
.GetSkShader(samplingOptions
));
1429 drawShader(rPosAry
, shader
);
1433 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
)
1435 if (checkInvalidSourceOrDestination(rPosAry
))
1438 assert(dynamic_cast<const SkiaSalBitmap
*>(&rSalBitmap
));
1439 const SkiaSalBitmap
& rSkiaSourceBitmap
= static_cast<const SkiaSalBitmap
&>(rSalBitmap
);
1441 drawBitmap(rPosAry
, rSkiaSourceBitmap
);
1444 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
,
1445 const SalBitmap
& rMaskBitmap
)
1447 drawAlphaBitmap(rPosAry
, rSalBitmap
, rMaskBitmap
);
1450 void SkiaSalGraphicsImpl::drawMask(const SalTwoRect
& rPosAry
, const SalBitmap
& rSalBitmap
,
1453 assert(dynamic_cast<const SkiaSalBitmap
*>(&rSalBitmap
));
1454 const SkiaSalBitmap
& skiaBitmap
= static_cast<const SkiaSalBitmap
&>(rSalBitmap
);
1457 SkShaders::Blend(SkBlendMode::kDstOut
, // VCL alpha is one-minus-alpha.
1458 SkShaders::Color(toSkColor(nMaskColor
)),
1459 skiaBitmap
.GetAlphaSkShader(makeSamplingOptions(rPosAry
, mScaling
))));
1462 std::shared_ptr
<SalBitmap
> SkiaSalGraphicsImpl::getBitmap(tools::Long nX
, tools::Long nY
,
1463 tools::Long nWidth
, tools::Long nHeight
)
1467 SAL_INFO("vcl.skia.trace",
1468 "getbitmap(" << this << "): " << SkIRect::MakeXYWH(nX
, nY
, nWidth
, nHeight
));
1470 // TODO makeImageSnapshot(rect) may copy the data, which may be a waste if this is used
1471 // e.g. for VirtualDevice's lame alpha blending, in which case the image will eventually end up
1472 // in blendAlphaBitmap(), where we could simply use the proper rect of the image.
1473 sk_sp
<SkImage
> image
= makeCheckedImageSnapshot(
1474 mSurface
, scaleRect(SkIRect::MakeXYWH(nX
, nY
, nWidth
, nHeight
), mScaling
));
1475 std::shared_ptr
<SkiaSalBitmap
> bitmap
= std::make_shared
<SkiaSalBitmap
>(image
);
1476 // If the surface is scaled for HiDPI, the bitmap needs to be scaled down, otherwise
1477 // it would have incorrect size from the API point of view. The DirectImage::Yes handling
1478 // in mergeCacheBitmaps() should access the original unscaled bitmap data to avoid
1479 // pointless scaling back and forth.
1482 if (!isUnitTestRunning())
1483 bitmap
->Scale(1.0 / mScaling
, 1.0 / mScaling
, goodScalingQuality());
1486 // Some tests require exact pixel values and would be confused by smooth-scaling.
1487 // And some draw something smooth and not smooth-scaling there would break the checks.
1488 if (isUnitTestRunning("BackendTest__testDrawHaflEllipseAAWithPolyLineB2D_")
1489 || isUnitTestRunning("BackendTest__testDrawRectAAWithLine_"))
1491 bitmap
->Scale(1.0 / mScaling
, 1.0 / mScaling
, goodScalingQuality());
1494 bitmap
->Scale(1.0 / mScaling
, 1.0 / mScaling
, BmpScaleFlag::NearestNeighbor
);
1500 Color
SkiaSalGraphicsImpl::getPixel(tools::Long nX
, tools::Long nY
)
1504 SAL_INFO("vcl.skia.trace", "getpixel(" << this << "): " << Point(nX
, nY
));
1506 // This is presumably slow, but getPixel() should be generally used only by unit tests.
1508 if (!bitmap
.tryAllocN32Pixels(mSurface
->width(), mSurface
->height()))
1510 if (!mSurface
->readPixels(bitmap
, 0, 0))
1512 return fromSkColor(bitmap
.getColor(nX
* mScaling
, nY
* mScaling
));
1515 void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon
const& rPoly
, SalInvert eFlags
)
1518 SAL_INFO("vcl.skia.trace", "invert(" << this << "): " << rPoly
<< ":" << int(eFlags
));
1521 aPath
.incReserve(rPoly
.count());
1522 addPolygonToPath(rPoly
, aPath
);
1523 aPath
.setFillType(SkPathFillType::kEvenOdd
);
1524 addUpdateRegion(aPath
.getBounds());
1525 SkAutoCanvasRestore
autoRestore(getDrawCanvas(), true);
1527 setBlendModeDifference(&aPaint
);
1528 // TrackFrame just inverts a dashed path around the polygon
1529 if (eFlags
== SalInvert::TrackFrame
)
1531 // TrackFrame is not supposed to paint outside of the polygon (usually rectangle),
1532 // but wider stroke width usually results in that, so ensure the requirement
1534 getDrawCanvas()->clipRect(aPath
.getBounds(), SkClipOp::kIntersect
, false);
1535 aPaint
.setStrokeWidth(2);
1536 constexpr float intervals
[] = { 4.0f
, 4.0f
};
1537 aPaint
.setStyle(SkPaint::kStroke_Style
);
1538 aPaint
.setPathEffect(SkDashPathEffect::Make(intervals
, SK_ARRAY_COUNT(intervals
), 0));
1539 aPaint
.setColor(SkColorSetARGB(255, 255, 255, 255));
1543 aPaint
.setColor(SkColorSetARGB(255, 255, 255, 255));
1544 aPaint
.setStyle(SkPaint::kFill_Style
);
1546 // N50 inverts in checker pattern
1547 if (eFlags
== SalInvert::N50
)
1549 // This creates 2x2 checker pattern bitmap
1550 // TODO Use createSkSurface() and cache the image
1552 aBitmap
.allocN32Pixels(2, 2);
1553 const SkPMColor white
= SkPreMultiplyARGB(0xFF, 0xFF, 0xFF, 0xFF);
1554 const SkPMColor black
= SkPreMultiplyARGB(0xFF, 0x00, 0x00, 0x00);
1555 SkPMColor
* scanline
;
1556 scanline
= aBitmap
.getAddr32(0, 0);
1557 *scanline
++ = white
;
1558 *scanline
++ = black
;
1559 scanline
= aBitmap
.getAddr32(0, 1);
1560 *scanline
++ = black
;
1561 *scanline
++ = white
;
1562 aBitmap
.setImmutable();
1563 // The bitmap is repeated in both directions the checker pattern is as big
1564 // as the polygon (usually rectangle)
1566 aBitmap
.makeShader(SkTileMode::kRepeat
, SkTileMode::kRepeat
, SkSamplingOptions()));
1569 getDrawCanvas()->drawPath(aPath
, aPaint
);
1573 void SkiaSalGraphicsImpl::invert(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
1574 tools::Long nHeight
, SalInvert eFlags
)
1576 basegfx::B2DRectangle
aRectangle(nX
, nY
, nX
+ nWidth
, nY
+ nHeight
);
1577 auto aRect
= basegfx::utils::createPolygonFromRect(aRectangle
);
1578 invert(aRect
, eFlags
);
1581 void SkiaSalGraphicsImpl::invert(sal_uInt32 nPoints
, const Point
* pPointArray
, SalInvert eFlags
)
1583 basegfx::B2DPolygon aPolygon
;
1584 aPolygon
.append(basegfx::B2DPoint(pPointArray
[0].getX(), pPointArray
[0].getY()), nPoints
);
1585 for (sal_uInt32 i
= 1; i
< nPoints
; ++i
)
1587 aPolygon
.setB2DPoint(i
, basegfx::B2DPoint(pPointArray
[i
].getX(), pPointArray
[i
].getY()));
1589 aPolygon
.setClosed(true);
1591 invert(aPolygon
, eFlags
);
1594 bool SkiaSalGraphicsImpl::drawEPS(tools::Long
, tools::Long
, tools::Long
, tools::Long
, void*,
1600 // Create SkImage from a bitmap and possibly an alpha mask (the usual VCL one-minus-alpha),
1601 // with the given target size. Result will be possibly cached, unless disabled.
1602 // Especially in raster mode scaling and alpha blending may be expensive if done repeatedly.
1603 sk_sp
<SkImage
> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap
& bitmap
,
1604 const SkiaSalBitmap
* alphaBitmap
,
1605 const Size
& targetSize
)
1608 assert(bitmap
.GetSize() == alphaBitmap
->GetSize());
1610 if (targetSize
.IsEmpty())
1612 if (alphaBitmap
&& alphaBitmap
->IsFullyOpaqueAsAlpha())
1613 alphaBitmap
= nullptr; // the alpha can be ignored
1614 if (bitmap
.PreferSkShader() && (!alphaBitmap
|| alphaBitmap
->PreferSkShader()))
1617 // If the bitmap has SkImage that matches the required size, try to use it, even
1618 // if it doesn't match bitmap.GetSize(). This can happen with delayed scaling.
1619 // This will catch cases such as some code pre-scaling the bitmap, which would make GetSkImage()
1620 // scale, changing GetImageKey() in the process so we'd have to re-cache, and then we'd need
1621 // to scale again in this function.
1622 bool bitmapReady
= false;
1623 bool alphaBitmapReady
= false;
1624 if (const sk_sp
<SkImage
>& image
= bitmap
.GetSkImage(DirectImage::Yes
))
1626 assert(!bitmap
.PreferSkShader());
1627 if (imageSize(image
) == targetSize
)
1630 // If the image usable and there's no alpha, then it matches exactly what's wanted.
1631 if (bitmapReady
&& !alphaBitmap
)
1632 return bitmap
.GetSkImage(DirectImage::Yes
);
1635 if (!alphaBitmap
->GetAlphaSkImage(DirectImage::Yes
)
1636 && alphaBitmap
->GetSkImage(DirectImage::Yes
)
1637 && imageSize(alphaBitmap
->GetSkImage(DirectImage::Yes
)) == targetSize
)
1639 // There's a usable non-alpha image, try to convert it to alpha.
1640 assert(!alphaBitmap
->PreferSkShader());
1641 const_cast<SkiaSalBitmap
*>(alphaBitmap
)->TryDirectConvertToAlphaNoScaling();
1643 if (const sk_sp
<SkImage
>& image
= alphaBitmap
->GetAlphaSkImage(DirectImage::Yes
))
1645 assert(!alphaBitmap
->PreferSkShader());
1646 if (imageSize(image
) == targetSize
)
1647 alphaBitmapReady
= true;
1651 if (bitmapReady
&& (!alphaBitmap
|| alphaBitmapReady
))
1653 // Try to find a cached image based on the already existing images.
1654 OString key
= makeCachedImageKey(bitmap
, alphaBitmap
, targetSize
, DirectImage::Yes
,
1656 if (sk_sp
<SkImage
> image
= findCachedImage(key
))
1658 assert(imageSize(image
) == targetSize
);
1663 // Probably not much point in caching of just doing a copy.
1664 if (alphaBitmap
== nullptr && targetSize
== bitmap
.GetSize())
1666 // Image too small to be worth caching if not scaling.
1667 if (targetSize
== bitmap
.GetSize() && targetSize
.Width() < 100 && targetSize
.Height() < 100)
1669 // GPU-accelerated drawing with SkShader should be fast enough to not need caching.
1672 // tdf#140925: But if this is such an extensive downscaling that caching the result
1673 // would noticeably reduce amount of data processed by the GPU on repeated usage, do it.
1674 int reduceRatio
= bitmap
.GetSize().Width() * bitmap
.GetSize().Height() / targetSize
.Width()
1675 / targetSize
.Height();
1676 if (reduceRatio
< 10)
1679 // In some cases (tdf#134237) the target size may be very large. In that case it's
1680 // better to rely on Skia to clip and draw only the necessary, rather than prepare
1681 // a very large image only to not use most of it.
1682 const Size drawAreaSize
= mClipRegion
.GetBoundRect().GetSize() * mScaling
;
1683 if (targetSize
.Width() > drawAreaSize
.Width() || targetSize
.Height() > drawAreaSize
.Height())
1685 // This is a bit tricky. The condition above just checks that at least a part of the resulting
1686 // image will not be used (it's larger then our drawing area). But this may often happen
1687 // when just scrolling a document with a large image, where the caching may very well be worth it.
1688 // Since the problem is mainly the cost of upscaling and then the size of the resulting bitmap,
1689 // compute a ratio of how much this is going to be scaled up, how much this is larger than
1690 // the drawing area, and then refuse to cache if it's too much.
1691 const double upscaleRatio
1692 = std::max(1.0, 1.0 * targetSize
.Width() / bitmap
.GetSize().Width()
1693 * targetSize
.Height() / bitmap
.GetSize().Height());
1694 const double oversizeRatio
= 1.0 * targetSize
.Width() / drawAreaSize
.Width()
1695 * targetSize
.Height() / drawAreaSize
.Height();
1696 const double ratio
= upscaleRatio
* oversizeRatio
;
1699 SAL_INFO("vcl.skia.trace", "mergecachebitmaps("
1700 << this << "): not caching, ratio:" << ratio
<< ", "
1701 << bitmap
.GetSize() << "->" << targetSize
<< " in "
1706 // Do not cache the result if it would take most of the cache and thus get evicted soon.
1707 if (targetSize
.Width() * targetSize
.Height() * 4 > maxImageCacheSize() * 0.7)
1710 // Use ready direct image if they are both available, now even the size doesn't matter
1711 // (we'll scale as necessary and it's better to scale from the original). Require only
1712 // that they are the same size, or that one prefers a shader or doesn't exist
1713 // (i.e. avoid two images of different size).
1714 bitmapReady
= bitmap
.GetSkImage(DirectImage::Yes
) != nullptr;
1715 alphaBitmapReady
= alphaBitmap
&& alphaBitmap
->GetAlphaSkImage(DirectImage::Yes
) != nullptr;
1716 if (bitmapReady
&& alphaBitmap
&& !alphaBitmapReady
&& !alphaBitmap
->PreferSkShader())
1717 bitmapReady
= false;
1718 if (alphaBitmapReady
&& !bitmapReady
&& bitmap
.PreferSkShader())
1719 alphaBitmapReady
= false;
1721 DirectImage bitmapType
= bitmapReady
? DirectImage::Yes
: DirectImage::No
;
1722 DirectImage alphaBitmapType
= alphaBitmapReady
? DirectImage::Yes
: DirectImage::No
;
1724 // Try to find a cached result, this time after possible delayed scaling.
1725 OString key
= makeCachedImageKey(bitmap
, alphaBitmap
, targetSize
, bitmapType
, alphaBitmapType
);
1726 if (sk_sp
<SkImage
> image
= findCachedImage(key
))
1728 assert(imageSize(image
) == targetSize
);
1734 sourceSize
= imageSize(bitmap
.GetSkImage(DirectImage::Yes
));
1735 else if (alphaBitmapReady
)
1736 sourceSize
= imageSize(alphaBitmap
->GetAlphaSkImage(DirectImage::Yes
));
1738 sourceSize
= bitmap
.GetSize();
1740 // Generate a new result and cache it.
1741 sk_sp
<SkSurface
> tmpSurface
1742 = createSkSurface(targetSize
, alphaBitmap
? kPremul_SkAlphaType
: bitmap
.alphaType());
1745 SkCanvas
* canvas
= tmpSurface
->getCanvas();
1746 SkAutoCanvasRestore
autoRestore(canvas
, true);
1748 SkSamplingOptions samplingOptions
;
1749 if (targetSize
!= sourceSize
)
1752 matrix
.set(SkMatrix::kMScaleX
, 1.0 * targetSize
.Width() / sourceSize
.Width());
1753 matrix
.set(SkMatrix::kMScaleY
, 1.0 * targetSize
.Height() / sourceSize
.Height());
1754 canvas
->concat(matrix
);
1755 if (!isUnitTestRunning()) // unittests want exact pixel values
1756 samplingOptions
= makeSamplingOptions(matrix
, 1);
1758 if (alphaBitmap
!= nullptr)
1760 canvas
->clear(SK_ColorTRANSPARENT
);
1762 SkShaders::Blend(SkBlendMode::kDstOut
, bitmap
.GetSkShader(samplingOptions
, bitmapType
),
1763 alphaBitmap
->GetAlphaSkShader(samplingOptions
, alphaBitmapType
)));
1764 canvas
->drawPaint(paint
);
1766 else if (bitmap
.PreferSkShader())
1768 paint
.setShader(bitmap
.GetSkShader(samplingOptions
, bitmapType
));
1769 canvas
->drawPaint(paint
);
1772 canvas
->drawImage(bitmap
.GetSkImage(bitmapType
), 0, 0, samplingOptions
, &paint
);
1774 SAL_INFO("vcl.skia.trace", "mergecachebitmaps(" << this << "): caching GPU downscaling:"
1775 << bitmap
.GetSize() << "->" << targetSize
);
1776 sk_sp
<SkImage
> image
= makeCheckedImageSnapshot(tmpSurface
);
1777 addCachedImage(key
, image
);
1781 OString
SkiaSalGraphicsImpl::makeCachedImageKey(const SkiaSalBitmap
& bitmap
,
1782 const SkiaSalBitmap
* alphaBitmap
,
1783 const Size
& targetSize
, DirectImage bitmapType
,
1784 DirectImage alphaBitmapType
)
1786 OString key
= OString::number(targetSize
.Width()) + "x" + OString::number(targetSize
.Height())
1787 + "_" + bitmap
.GetImageKey(bitmapType
);
1789 key
+= "_" + alphaBitmap
->GetAlphaImageKey(alphaBitmapType
);
1793 bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect
& rPosAry
, const SalBitmap
& rSourceBitmap
,
1794 const SalBitmap
& rAlphaBitmap
)
1796 assert(dynamic_cast<const SkiaSalBitmap
*>(&rSourceBitmap
));
1797 assert(dynamic_cast<const SkiaSalBitmap
*>(&rAlphaBitmap
));
1798 const SkiaSalBitmap
& rSkiaSourceBitmap
= static_cast<const SkiaSalBitmap
&>(rSourceBitmap
);
1799 const SkiaSalBitmap
& rSkiaAlphaBitmap
= static_cast<const SkiaSalBitmap
&>(rAlphaBitmap
);
1800 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1801 // alpha blending or scaling.
1802 SalTwoRect
imagePosAry(rPosAry
);
1803 Size imageSize
= rSourceBitmap
.GetSize();
1804 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1805 if ((rPosAry
.mnSrcWidth
!= rPosAry
.mnDestWidth
|| rPosAry
.mnSrcHeight
!= rPosAry
.mnDestHeight
)
1806 && rPosAry
.mnSrcX
== 0 && rPosAry
.mnSrcY
== 0
1807 && rPosAry
.mnSrcWidth
== rSourceBitmap
.GetSize().Width()
1808 && rPosAry
.mnSrcHeight
== rSourceBitmap
.GetSize().Height())
1810 imagePosAry
.mnSrcWidth
= imagePosAry
.mnDestWidth
;
1811 imagePosAry
.mnSrcHeight
= imagePosAry
.mnDestHeight
;
1812 imageSize
= Size(imagePosAry
.mnSrcWidth
, imagePosAry
.mnSrcHeight
);
1814 sk_sp
<SkImage
> image
1815 = mergeCacheBitmaps(rSkiaSourceBitmap
, &rSkiaAlphaBitmap
, imageSize
* mScaling
);
1817 drawImage(imagePosAry
, image
, mScaling
);
1818 else if (rSkiaAlphaBitmap
.IsFullyOpaqueAsAlpha()
1819 && !rSkiaSourceBitmap
.PreferSkShader()) // alpha can be ignored
1820 drawBitmap(rPosAry
, rSkiaSourceBitmap
);
1824 SkBlendMode::kDstOut
, // VCL alpha is one-minus-alpha.
1825 rSkiaSourceBitmap
.GetSkShader(makeSamplingOptions(rPosAry
, mScaling
)),
1826 rSkiaAlphaBitmap
.GetAlphaSkShader(makeSamplingOptions(rPosAry
, mScaling
))));
1830 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect
& rPosAry
, const SkiaSalBitmap
& bitmap
,
1831 SkBlendMode blendMode
)
1833 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1835 SalTwoRect
imagePosAry(rPosAry
);
1836 Size imageSize
= bitmap
.GetSize();
1837 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1838 if ((rPosAry
.mnSrcWidth
!= rPosAry
.mnDestWidth
|| rPosAry
.mnSrcHeight
!= rPosAry
.mnDestHeight
)
1839 && rPosAry
.mnSrcX
== 0 && rPosAry
.mnSrcY
== 0
1840 && rPosAry
.mnSrcWidth
== bitmap
.GetSize().Width()
1841 && rPosAry
.mnSrcHeight
== bitmap
.GetSize().Height())
1843 imagePosAry
.mnSrcWidth
= imagePosAry
.mnDestWidth
;
1844 imagePosAry
.mnSrcHeight
= imagePosAry
.mnDestHeight
;
1845 imageSize
= Size(imagePosAry
.mnSrcWidth
, imagePosAry
.mnSrcHeight
);
1847 sk_sp
<SkImage
> image
= mergeCacheBitmaps(bitmap
, nullptr, imageSize
* mScaling
);
1849 drawImage(imagePosAry
, image
, mScaling
, blendMode
);
1850 else if (bitmap
.PreferSkShader())
1851 drawShader(rPosAry
, bitmap
.GetSkShader(makeSamplingOptions(rPosAry
, mScaling
)), blendMode
);
1853 drawImage(rPosAry
, bitmap
.GetSkImage(), 1, blendMode
);
1856 void SkiaSalGraphicsImpl::drawImage(const SalTwoRect
& rPosAry
, const sk_sp
<SkImage
>& aImage
,
1857 int srcScaling
, SkBlendMode eBlendMode
)
1860 = SkRect::MakeXYWH(rPosAry
.mnSrcX
, rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
);
1861 if (srcScaling
!= 1)
1862 aSourceRect
= scaleRect(aSourceRect
, srcScaling
);
1863 SkRect aDestinationRect
= SkRect::MakeXYWH(rPosAry
.mnDestX
, rPosAry
.mnDestY
,
1864 rPosAry
.mnDestWidth
, rPosAry
.mnDestHeight
);
1867 aPaint
.setBlendMode(eBlendMode
);
1870 SAL_INFO("vcl.skia.trace",
1871 "drawimage(" << this << "): " << rPosAry
<< ":" << SkBlendMode_Name(eBlendMode
));
1872 addUpdateRegion(aDestinationRect
);
1873 getDrawCanvas()->drawImageRect(aImage
, aSourceRect
, aDestinationRect
,
1874 makeSamplingOptions(rPosAry
, mScaling
, srcScaling
), &aPaint
,
1875 SkCanvas::kFast_SrcRectConstraint
);
1876 ++mPendingOperationsToFlush
; // tdf#136369
1880 // SkShader can be used to merge multiple bitmaps with appropriate blend modes (e.g. when
1881 // merging a bitmap with its alpha mask).
1882 void SkiaSalGraphicsImpl::drawShader(const SalTwoRect
& rPosAry
, const sk_sp
<SkShader
>& shader
,
1883 SkBlendMode blendMode
)
1886 SAL_INFO("vcl.skia.trace", "drawshader(" << this << "): " << rPosAry
);
1887 SkRect destinationRect
= SkRect::MakeXYWH(rPosAry
.mnDestX
, rPosAry
.mnDestY
, rPosAry
.mnDestWidth
,
1888 rPosAry
.mnDestHeight
);
1889 addUpdateRegion(destinationRect
);
1891 paint
.setBlendMode(blendMode
);
1892 paint
.setShader(shader
);
1893 SkCanvas
* canvas
= getDrawCanvas();
1894 // Scaling needs to be done explicitly using a matrix.
1895 SkAutoCanvasRestore
autoRestore(canvas
, true);
1896 SkMatrix matrix
= SkMatrix::Translate(rPosAry
.mnDestX
, rPosAry
.mnDestY
)
1897 * SkMatrix::Scale(1.0 * rPosAry
.mnDestWidth
/ rPosAry
.mnSrcWidth
,
1898 1.0 * rPosAry
.mnDestHeight
/ rPosAry
.mnSrcHeight
)
1899 * SkMatrix::Translate(-rPosAry
.mnSrcX
, -rPosAry
.mnSrcY
);
1901 // Handle floating point imprecisions, round p1 to 2 decimal places.
1902 auto compareRounded
= [](const SkPoint
& p1
, const SkPoint
& p2
) {
1903 return rtl::math::round(p1
.x(), 2) == p2
.x() && rtl::math::round(p1
.y(), 2) == p2
.y();
1906 assert(compareRounded(matrix
.mapXY(rPosAry
.mnSrcX
, rPosAry
.mnSrcY
),
1907 SkPoint::Make(rPosAry
.mnDestX
, rPosAry
.mnDestY
)));
1908 assert(compareRounded(
1909 matrix
.mapXY(rPosAry
.mnSrcX
+ rPosAry
.mnSrcWidth
, rPosAry
.mnSrcY
+ rPosAry
.mnSrcHeight
),
1910 SkPoint::Make(rPosAry
.mnDestX
+ rPosAry
.mnDestWidth
,
1911 rPosAry
.mnDestY
+ rPosAry
.mnDestHeight
)));
1912 canvas
->concat(matrix
);
1914 = SkRect::MakeXYWH(rPosAry
.mnSrcX
, rPosAry
.mnSrcY
, rPosAry
.mnSrcWidth
, rPosAry
.mnSrcHeight
);
1915 canvas
->drawRect(sourceRect
, paint
);
1919 bool SkiaSalGraphicsImpl::hasFastDrawTransformedBitmap() const
1921 // Return true even in raster mode, even that way Skia is faster than e.g. GraphicObject
1922 // trying to handle stuff manually.
1926 // Whether applying matrix needs image smoothing for the transformation.
1927 static bool matrixNeedsHighQuality(const SkMatrix
& matrix
)
1929 if (matrix
.isIdentity())
1931 if (matrix
.isScaleTranslate())
1933 if (abs(matrix
.getScaleX()) == 1 && abs(matrix
.getScaleY()) == 1)
1934 return false; // Only at most flipping and keeping the size.
1937 assert(!matrix
.hasPerspective()); // we do not use this
1938 if (matrix
.getScaleX() == 0 && matrix
.getScaleY() == 0)
1940 // Rotating 90 or 270 degrees while keeping the size.
1941 if ((matrix
.getSkewX() == 1 && matrix
.getSkewY() == -1)
1942 || (matrix
.getSkewX() == -1 && matrix
.getSkewY() == 1))
1950 bool matrixNeedsHighQuality(const SkMatrix
& matrix
) { return ::matrixNeedsHighQuality(matrix
); }
1953 bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint
& rNull
,
1954 const basegfx::B2DPoint
& rX
,
1955 const basegfx::B2DPoint
& rY
,
1956 const SalBitmap
& rSourceBitmap
,
1957 const SalBitmap
* pAlphaBitmap
, double fAlpha
)
1959 assert(dynamic_cast<const SkiaSalBitmap
*>(&rSourceBitmap
));
1960 assert(!pAlphaBitmap
|| dynamic_cast<const SkiaSalBitmap
*>(pAlphaBitmap
));
1962 const SkiaSalBitmap
& rSkiaBitmap
= static_cast<const SkiaSalBitmap
&>(rSourceBitmap
);
1963 const SkiaSalBitmap
* pSkiaAlphaBitmap
= static_cast<const SkiaSalBitmap
*>(pAlphaBitmap
);
1965 if (pSkiaAlphaBitmap
&& pSkiaAlphaBitmap
->IsFullyOpaqueAsAlpha())
1966 pSkiaAlphaBitmap
= nullptr; // the alpha can be ignored
1968 // Setup the image transformation,
1969 // using the rNull, rX, rY points as destinations for the (0,0), (Width,0), (0,Height) source points.
1970 const basegfx::B2DVector aXRel
= rX
- rNull
;
1971 const basegfx::B2DVector aYRel
= rY
- rNull
;
1974 SAL_INFO("vcl.skia.trace", "drawtransformedbitmap(" << this << "): " << rSourceBitmap
.GetSize()
1975 << " " << rNull
<< ":" << rX
<< ":" << rY
);
1977 addUpdateRegion(SkRect::MakeWH(GetWidth(), GetHeight())); // can't tell, use whole area
1978 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1979 // alpha blending or scaling.
1980 // The extra fAlpha blending is not cached, with the assumption that it usually gradually changes
1981 // for each invocation.
1982 // Pass size * mScaling to mergeCacheBitmaps() so that it prepares the size that will be needed
1983 // after the mScaling-scaling matrix, but otherwise calculate everything else using the VCL coordinates.
1984 Size
imageSize(round(aXRel
.getLength()), round(aYRel
.getLength()));
1985 sk_sp
<SkImage
> imageToDraw
1986 = mergeCacheBitmaps(rSkiaBitmap
, pSkiaAlphaBitmap
, imageSize
* mScaling
);
1990 // Round sizes for scaling, so that sub-pixel differences don't
1991 // trigger unnecessary scaling. Image has already been scaled
1992 // by mergeCacheBitmaps() and we shouldn't scale here again
1993 // unless the drawing is also skewed.
1994 matrix
.set(SkMatrix::kMScaleX
, round(aXRel
.getX()) / imageSize
.Width());
1995 matrix
.set(SkMatrix::kMScaleY
, round(aYRel
.getY()) / imageSize
.Height());
1996 matrix
.set(SkMatrix::kMSkewY
, aXRel
.getY() / imageSize
.Width());
1997 matrix
.set(SkMatrix::kMSkewX
, aYRel
.getX() / imageSize
.Height());
1998 matrix
.set(SkMatrix::kMTransX
, rNull
.getX());
1999 matrix
.set(SkMatrix::kMTransY
, rNull
.getY());
2000 SkCanvas
* canvas
= getDrawCanvas();
2001 SkAutoCanvasRestore
autoRestore(canvas
, true);
2002 canvas
->concat(matrix
);
2003 SkSamplingOptions samplingOptions
;
2004 // If the matrix changes geometry, we need to smooth-scale. If there's mScaling,
2005 // that's already been handled by mergeCacheBitmaps().
2006 if (matrixNeedsHighQuality(matrix
))
2007 samplingOptions
= makeSamplingOptions(matrix
, 1);
2010 // Specify sizes to scale the image size back if needed (because of mScaling).
2011 SkRect dstRect
= SkRect::MakeWH(imageSize
.Width(), imageSize
.Height());
2012 SkRect srcRect
= SkRect::MakeWH(imageToDraw
->width(), imageToDraw
->height());
2013 canvas
->drawImageRect(imageToDraw
, srcRect
, dstRect
, samplingOptions
, nullptr,
2014 SkCanvas::kFast_SrcRectConstraint
);
2019 // Scale the image size back if needed.
2020 SkMatrix scale
= SkMatrix::Scale(1.0 / mScaling
, 1.0 / mScaling
);
2021 paint
.setShader(SkShaders::Blend(
2022 SkBlendMode::kDstIn
, imageToDraw
->makeShader(samplingOptions
, &scale
),
2023 SkShaders::Color(SkColorSetARGB(fAlpha
* 255, 0, 0, 0))));
2024 canvas
->drawRect(SkRect::MakeWH(imageSize
.Width(), imageSize
.Height()), paint
);
2030 const Size aSize
= rSourceBitmap
.GetSize();
2031 matrix
.set(SkMatrix::kMScaleX
, aXRel
.getX() / aSize
.Width());
2032 matrix
.set(SkMatrix::kMScaleY
, aYRel
.getY() / aSize
.Height());
2033 matrix
.set(SkMatrix::kMSkewY
, aXRel
.getY() / aSize
.Width());
2034 matrix
.set(SkMatrix::kMSkewX
, aYRel
.getX() / aSize
.Height());
2035 matrix
.set(SkMatrix::kMTransX
, rNull
.getX());
2036 matrix
.set(SkMatrix::kMTransY
, rNull
.getY());
2037 SkCanvas
* canvas
= getDrawCanvas();
2038 SkAutoCanvasRestore
autoRestore(canvas
, true);
2039 canvas
->concat(matrix
);
2040 SkSamplingOptions samplingOptions
;
2041 if (matrixNeedsHighQuality(matrix
) || (mScaling
!= 1 && !isUnitTestRunning()))
2042 samplingOptions
= makeSamplingOptions(matrix
, mScaling
);
2043 if (pSkiaAlphaBitmap
)
2046 paint
.setShader(SkShaders::Blend(SkBlendMode::kDstOut
, // VCL alpha is one-minus-alpha.
2047 rSkiaBitmap
.GetSkShader(samplingOptions
),
2048 pSkiaAlphaBitmap
->GetAlphaSkShader(samplingOptions
)));
2051 SkShaders::Blend(SkBlendMode::kDstIn
, paint
.refShader(),
2052 SkShaders::Color(SkColorSetARGB(fAlpha
* 255, 0, 0, 0))));
2053 canvas
->drawRect(SkRect::MakeWH(aSize
.Width(), aSize
.Height()), paint
);
2055 else if (rSkiaBitmap
.PreferSkShader() || fAlpha
!= 1.0)
2058 paint
.setShader(rSkiaBitmap
.GetSkShader(samplingOptions
));
2061 SkShaders::Blend(SkBlendMode::kDstIn
, paint
.refShader(),
2062 SkShaders::Color(SkColorSetARGB(fAlpha
* 255, 0, 0, 0))));
2063 canvas
->drawRect(SkRect::MakeWH(aSize
.Width(), aSize
.Height()), paint
);
2067 canvas
->drawImage(rSkiaBitmap
.GetSkImage(), 0, 0, samplingOptions
);
2074 bool SkiaSalGraphicsImpl::drawAlphaRect(tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
2075 tools::Long nHeight
, sal_uInt8 nTransparency
)
2077 privateDrawAlphaRect(nX
, nY
, nWidth
, nHeight
, nTransparency
/ 100.0);
2081 bool SkiaSalGraphicsImpl::drawGradient(const tools::PolyPolygon
& rPolyPolygon
,
2082 const Gradient
& rGradient
)
2084 if (rGradient
.GetStyle() != GradientStyle::Linear
2085 && rGradient
.GetStyle() != GradientStyle::Axial
2086 && rGradient
.GetStyle() != GradientStyle::Radial
)
2087 return false; // unsupported
2088 if (rGradient
.GetSteps() != 0)
2089 return false; // We can't tell Skia how many colors to use in the gradient.
2091 SAL_INFO("vcl.skia.trace", "drawgradient(" << this << "): " << rPolyPolygon
.getB2DPolyPolygon()
2092 << ":" << static_cast<int>(rGradient
.GetStyle()));
2093 tools::Rectangle
boundRect(rPolyPolygon
.GetBoundRect());
2094 if (boundRect
.IsEmpty())
2097 if (rPolyPolygon
.IsRect())
2099 // Rect->Polygon conversion loses the right and bottom edge, fix that.
2100 path
.addRect(SkRect::MakeXYWH(boundRect
.getX(), boundRect
.getY(), boundRect
.GetWidth(),
2101 boundRect
.GetHeight()));
2102 boundRect
.AdjustRight(1);
2103 boundRect
.AdjustBottom(1);
2106 addPolyPolygonToPath(rPolyPolygon
.getB2DPolyPolygon(), path
);
2107 path
.setFillType(SkPathFillType::kEvenOdd
);
2108 addUpdateRegion(path
.getBounds());
2110 Gradient
aGradient(rGradient
);
2111 tools::Rectangle aBoundRect
;
2113 aGradient
.SetAngle(aGradient
.GetAngle() + 2700_deg10
);
2114 aGradient
.GetBoundRect(boundRect
, aBoundRect
, aCenter
);
2117 = toSkColorWithIntensity(rGradient
.GetStartColor(), rGradient
.GetStartIntensity());
2118 SkColor endColor
= toSkColorWithIntensity(rGradient
.GetEndColor(), rGradient
.GetEndIntensity());
2120 sk_sp
<SkShader
> shader
;
2121 if (rGradient
.GetStyle() == GradientStyle::Linear
)
2123 tools::Polygon
aPoly(aBoundRect
);
2124 aPoly
.Rotate(aCenter
, aGradient
.GetAngle() % 3600_deg10
);
2125 SkPoint points
[2] = { SkPoint::Make(toSkX(aPoly
[0].X()), toSkY(aPoly
[0].Y())),
2126 SkPoint::Make(toSkX(aPoly
[1].X()), toSkY(aPoly
[1].Y())) };
2127 SkColor colors
[2] = { startColor
, endColor
};
2128 SkScalar pos
[2] = { SkDoubleToScalar(aGradient
.GetBorder() / 100.0), 1.0 };
2129 shader
= SkGradientShader::MakeLinear(points
, colors
, pos
, 2, SkTileMode::kClamp
);
2131 else if (rGradient
.GetStyle() == GradientStyle::Axial
)
2133 tools::Polygon
aPoly(aBoundRect
);
2134 aPoly
.Rotate(aCenter
, aGradient
.GetAngle() % 3600_deg10
);
2135 SkPoint points
[2] = { SkPoint::Make(toSkX(aPoly
[0].X()), toSkY(aPoly
[0].Y())),
2136 SkPoint::Make(toSkX(aPoly
[1].X()), toSkY(aPoly
[1].Y())) };
2137 SkColor colors
[3] = { endColor
, startColor
, endColor
};
2138 SkScalar border
= SkDoubleToScalar(aGradient
.GetBorder() / 100.0);
2140 = { std::min
<SkScalar
>(border
, 0.5), 0.5, std::max
<SkScalar
>(1 - border
, 0.5) };
2141 shader
= SkGradientShader::MakeLinear(points
, colors
, pos
, 3, SkTileMode::kClamp
);
2145 // Move the center by (-1,-1) (the default VCL algorithm is a bit off-center that way,
2146 // Skia is the opposite way).
2147 SkPoint center
= SkPoint::Make(toSkX(aCenter
.X()) - 1, toSkY(aCenter
.Y()) - 1);
2148 SkScalar radius
= std::max(aBoundRect
.GetWidth() / 2.0, aBoundRect
.GetHeight() / 2.0);
2149 SkColor colors
[2] = { endColor
, startColor
};
2150 SkScalar pos
[2] = { SkDoubleToScalar(aGradient
.GetBorder() / 100.0), 1.0 };
2151 shader
= SkGradientShader::MakeRadial(center
, radius
, colors
, pos
, 2, SkTileMode::kClamp
);
2155 paint
.setAntiAlias(mParent
.getAntiAlias());
2156 paint
.setShader(shader
);
2157 getDrawCanvas()->drawPath(path
, paint
);
2162 bool SkiaSalGraphicsImpl::implDrawGradient(const basegfx::B2DPolyPolygon
& rPolyPolygon
,
2163 const SalGradient
& rGradient
)
2166 SAL_INFO("vcl.skia.trace",
2167 "impldrawgradient(" << this << "): " << rPolyPolygon
<< ":" << rGradient
.maPoint1
2168 << "->" << rGradient
.maPoint2
<< ":" << rGradient
.maStops
.size());
2171 addPolyPolygonToPath(rPolyPolygon
, path
);
2172 path
.setFillType(SkPathFillType::kEvenOdd
);
2173 addUpdateRegion(path
.getBounds());
2176 = { SkPoint::Make(toSkX(rGradient
.maPoint1
.getX()), toSkY(rGradient
.maPoint1
.getY())),
2177 SkPoint::Make(toSkX(rGradient
.maPoint2
.getX()), toSkY(rGradient
.maPoint2
.getY())) };
2178 std::vector
<SkColor
> colors
;
2179 std::vector
<SkScalar
> pos
;
2180 for (const SalGradientStop
& stop
: rGradient
.maStops
)
2182 colors
.emplace_back(toSkColor(stop
.maColor
));
2183 pos
.emplace_back(stop
.mfOffset
);
2185 sk_sp
<SkShader
> shader
= SkGradientShader::MakeLinear(points
, colors
.data(), pos
.data(),
2186 colors
.size(), SkTileMode::kDecal
);
2188 paint
.setAntiAlias(mParent
.getAntiAlias());
2189 paint
.setShader(shader
);
2190 getDrawCanvas()->drawPath(path
, paint
);
2195 static double toRadian(Degree10 degree10th
) { return toRadians(3600_deg10
- degree10th
); }
2196 static double toCos(Degree10 degree10th
) { return SkScalarCos(toRadian(degree10th
)); }
2197 static double toSin(Degree10 degree10th
) { return SkScalarSin(toRadian(degree10th
)); }
2199 void SkiaSalGraphicsImpl::drawGenericLayout(const GenericSalLayout
& layout
, Color textColor
,
2200 const SkFont
& font
, const SkFont
& verticalFont
)
2203 std::vector
<SkGlyphID
> glyphIds
;
2204 std::vector
<SkRSXform
> glyphForms
;
2205 std::vector
<bool> verticals
;
2206 glyphIds
.reserve(256);
2207 glyphForms
.reserve(256);
2208 verticals
.reserve(256);
2210 const GlyphItem
* pGlyph
;
2212 while (layout
.GetNextGlyph(&pGlyph
, aPos
, nStart
))
2214 glyphIds
.push_back(pGlyph
->glyphId());
2215 Degree10 angle
= layout
.GetOrientation();
2216 if (pGlyph
->IsVertical())
2218 SkRSXform form
= SkRSXform::Make(toCos(angle
), toSin(angle
), aPos
.X(), aPos
.Y());
2219 glyphForms
.emplace_back(std::move(form
));
2220 verticals
.emplace_back(pGlyph
->IsVertical());
2222 if (glyphIds
.empty())
2226 auto getBoundRect
= [&layout
]() {
2227 tools::Rectangle rect
;
2228 layout
.GetBoundRect(rect
);
2231 SAL_INFO("vcl.skia.trace", "drawtextblob(" << this << "): " << getBoundRect() << ", "
2232 << glyphIds
.size() << " glyphs, " << textColor
);
2234 // Vertical glyphs need a different font, so split drawing into runs that each
2235 // draw only consecutive horizontal or vertical glyphs.
2236 std::vector
<bool>::const_iterator pos
= verticals
.cbegin();
2237 std::vector
<bool>::const_iterator end
= verticals
.cend();
2240 bool verticalRun
= *pos
;
2241 std::vector
<bool>::const_iterator rangeEnd
= std::find(pos
+ 1, end
, !verticalRun
);
2242 size_t index
= pos
- verticals
.cbegin();
2243 size_t count
= rangeEnd
- pos
;
2244 sk_sp
<SkTextBlob
> textBlob
= SkTextBlob::MakeFromRSXform(
2245 glyphIds
.data() + index
, count
* sizeof(SkGlyphID
), glyphForms
.data() + index
,
2246 verticalRun
? verticalFont
: font
, SkTextEncoding::kGlyphID
);
2247 addUpdateRegion(textBlob
->bounds());
2249 paint
.setColor(toSkColor(textColor
));
2250 getDrawCanvas()->drawTextBlob(textBlob
, 0, 0, paint
);
2256 bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType eType
) const
2260 case OutDevSupportType::B2DDraw
:
2261 case OutDevSupportType::TransparentRect
:
2268 static int getScaling()
2270 // It makes sense to support the debugging flag on all platforms
2271 // for unittests purpose, even if the actual windows cannot do it.
2272 if (const char* env
= getenv("SAL_FORCE_HIDPI_SCALING"))
2277 int SkiaSalGraphicsImpl::getWindowScaling() const
2279 static const int scaling
= getScaling();
2283 void SkiaSalGraphicsImpl::dump(const char* file
) const
2285 assert(mSurface
.get());
2286 SkiaHelper::dump(mSurface
, file
);
2289 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */