Update git submodules
[LibreOffice.git] / vcl / skia / gdiimpl.cxx
blob579a2d98d7faab665cce2ce88dee40c3520b9254
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <skia/gdiimpl.hxx>
22 #include <salgdi.hxx>
23 #include <skia/salbmp.hxx>
24 #include <vcl/idle.hxx>
25 #include <vcl/svapp.hxx>
26 #include <tools/lazydelete.hxx>
27 #include <vcl/gradient.hxx>
28 #include <vcl/skia/SkiaHelper.hxx>
29 #include <skia/utils.hxx>
30 #include <skia/zone.hxx>
31 #include <tools/debug.hxx>
33 #include <SkBitmap.h>
34 #include <SkCanvas.h>
35 #include <SkGradientShader.h>
36 #include <SkPath.h>
37 #include <SkRegion.h>
38 #include <SkPathEffect.h>
39 #include <SkDashPathEffect.h>
40 #include <GrBackendSurface.h>
41 #include <SkTextBlob.h>
42 #include <SkRSXform.h>
44 #include <numeric>
45 #include <sstream>
47 #include <basegfx/polygon/b2dpolygontools.hxx>
48 #include <basegfx/polygon/b2dpolypolygontools.hxx>
49 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
50 #include <o3tl/sorted_vector.hxx>
51 #include <rtl/math.hxx>
53 using namespace SkiaHelper;
55 namespace
57 // Create Skia Path from B2DPolygon
58 // Note that polygons generally have the complication that when used
59 // for area (fill) operations they usually miss the right-most and
60 // bottom-most line of pixels of the bounding rectangle (see
61 // https://lists.freedesktop.org/archives/libreoffice/2019-November/083709.html).
62 // So be careful with rectangle->polygon conversions (generally avoid them).
63 void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath, sal_uInt32 nFirstIndex,
64 sal_uInt32 nLastIndex, const sal_uInt32 nPointCount, const bool bClosePath,
65 const bool bHasCurves, bool* hasOnlyOrthogonal = nullptr)
67 assert(nFirstIndex < nPointCount || (nFirstIndex == 0 && nPointCount == 0));
68 assert(nLastIndex <= nPointCount);
70 if (nPointCount <= 1)
71 return;
73 bool bFirst = true;
74 sal_uInt32 nPreviousIndex = nFirstIndex == 0 ? nPointCount - 1 : nFirstIndex - 1;
75 basegfx::B2DPoint aPreviousPoint = rPolygon.getB2DPoint(nPreviousIndex);
77 for (sal_uInt32 nIndex = nFirstIndex; nIndex <= nLastIndex; nIndex++)
79 if (nIndex == nPointCount && !bClosePath)
80 continue;
82 // Make sure we loop the last point to first point
83 sal_uInt32 nCurrentIndex = nIndex % nPointCount;
84 basegfx::B2DPoint aCurrentPoint = rPolygon.getB2DPoint(nCurrentIndex);
86 if (bFirst)
88 rPath.moveTo(aCurrentPoint.getX(), aCurrentPoint.getY());
89 bFirst = false;
91 else if (!bHasCurves)
93 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY());
94 // If asked for, check whether the polygon has a line that is not
95 // strictly horizontal or vertical.
96 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
97 && aCurrentPoint.getY() != aPreviousPoint.getY())
98 *hasOnlyOrthogonal = false;
100 else
102 basegfx::B2DPoint aPreviousControlPoint = rPolygon.getNextControlPoint(nPreviousIndex);
103 basegfx::B2DPoint aCurrentControlPoint = rPolygon.getPrevControlPoint(nCurrentIndex);
105 if (aPreviousControlPoint.equal(aPreviousPoint)
106 && aCurrentControlPoint.equal(aCurrentPoint))
108 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY()); // a straight line
109 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
110 && aCurrentPoint.getY() != aPreviousPoint.getY())
111 *hasOnlyOrthogonal = false;
113 else
115 if (aPreviousControlPoint.equal(aPreviousPoint))
117 aPreviousControlPoint
118 = aPreviousPoint + ((aPreviousControlPoint - aCurrentPoint) * 0.0005);
120 if (aCurrentControlPoint.equal(aCurrentPoint))
122 aCurrentControlPoint
123 = aCurrentPoint + ((aCurrentControlPoint - aPreviousPoint) * 0.0005);
125 rPath.cubicTo(aPreviousControlPoint.getX(), aPreviousControlPoint.getY(),
126 aCurrentControlPoint.getX(), aCurrentControlPoint.getY(),
127 aCurrentPoint.getX(), aCurrentPoint.getY());
128 if (hasOnlyOrthogonal != nullptr)
129 *hasOnlyOrthogonal = false;
132 aPreviousPoint = aCurrentPoint;
133 nPreviousIndex = nCurrentIndex;
135 if (bClosePath && nFirstIndex == 0 && nLastIndex == nPointCount)
137 rPath.close();
141 void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath,
142 bool* hasOnlyOrthogonal = nullptr)
144 addPolygonToPath(rPolygon, rPath, 0, rPolygon.count(), rPolygon.count(), rPolygon.isClosed(),
145 rPolygon.areControlPointsUsed(), hasOnlyOrthogonal);
148 void addPolyPolygonToPath(const basegfx::B2DPolyPolygon& rPolyPolygon, SkPath& rPath,
149 bool* hasOnlyOrthogonal = nullptr)
151 const sal_uInt32 nPolygonCount(rPolyPolygon.count());
153 if (nPolygonCount == 0)
154 return;
156 sal_uInt32 nPointCount = 0;
157 for (const auto& rPolygon : rPolyPolygon)
158 nPointCount += rPolygon.count() * 3; // because cubicTo is 3 elements
159 rPath.incReserve(nPointCount);
161 for (const auto& rPolygon : rPolyPolygon)
163 addPolygonToPath(rPolygon, rPath, hasOnlyOrthogonal);
167 // Check if the given polygon contains a straight line. If not, it consists
168 // solely of curves.
169 bool polygonContainsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
171 if (!rPolyPolygon.areControlPointsUsed())
172 return true; // no curves at all
173 for (const auto& rPolygon : rPolyPolygon)
175 const sal_uInt32 nPointCount(rPolygon.count());
176 bool bFirst = true;
178 const bool bClosePath(rPolygon.isClosed());
180 sal_uInt32 nCurrentIndex = 0;
181 sal_uInt32 nPreviousIndex = nPointCount - 1;
183 basegfx::B2DPoint aCurrentPoint;
184 basegfx::B2DPoint aPreviousPoint;
186 for (sal_uInt32 nIndex = 0; nIndex <= nPointCount; nIndex++)
188 if (nIndex == nPointCount && !bClosePath)
189 continue;
191 // Make sure we loop the last point to first point
192 nCurrentIndex = nIndex % nPointCount;
193 if (bFirst)
194 bFirst = false;
195 else
197 basegfx::B2DPoint aPreviousControlPoint
198 = rPolygon.getNextControlPoint(nPreviousIndex);
199 basegfx::B2DPoint aCurrentControlPoint
200 = rPolygon.getPrevControlPoint(nCurrentIndex);
202 if (aPreviousControlPoint.equal(aPreviousPoint)
203 && aCurrentControlPoint.equal(aCurrentPoint))
205 return true; // found a straight line
208 aPreviousPoint = aCurrentPoint;
209 nPreviousIndex = nCurrentIndex;
212 return false; // no straight line found
215 // returns true if the source or destination rectangles are invalid
216 bool checkInvalidSourceOrDestination(SalTwoRect const& rPosAry)
218 return rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
219 || rPosAry.mnDestHeight <= 0;
222 std::string dumpOptionalColor(const std::optional<Color>& c)
224 std::ostringstream oss;
225 if (c)
226 oss << *c;
227 else
228 oss << "no color";
230 return std::move(oss).str(); // optimized in C++20
233 } // end anonymous namespace
235 // Class that triggers flushing the backing buffer when idle.
236 class SkiaFlushIdle : public Idle
238 SkiaSalGraphicsImpl* mpGraphics;
239 #ifndef NDEBUG
240 char* debugname;
241 #endif
243 public:
244 explicit SkiaFlushIdle(SkiaSalGraphicsImpl* pGraphics)
245 : Idle(get_debug_name(pGraphics))
246 , mpGraphics(pGraphics)
248 // We don't want to be swapping before we've painted.
249 SetPriority(TaskPriority::POST_PAINT);
251 #ifndef NDEBUG
252 virtual ~SkiaFlushIdle() { free(debugname); }
253 #endif
254 const char* get_debug_name(SkiaSalGraphicsImpl* pGraphics)
256 #ifndef NDEBUG
257 // Idle keeps just a pointer, so we need to store the string
258 debugname = strdup(
259 OString("skia idle 0x" + OString::number(reinterpret_cast<sal_uIntPtr>(pGraphics), 16))
260 .getStr());
261 return debugname;
262 #else
263 (void)pGraphics;
264 return "skia idle";
265 #endif
268 virtual void Invoke() override
270 mpGraphics->performFlush();
271 Stop();
272 #ifdef MACOSX
273 // tdf#157312 and tdf#163945 Lower Skia flush timer priority on macOS
274 // On macOS, flushing with Skia/Metal is noticeably slower than
275 // with Skia/Raster. So lower the flush timer priority to
276 // TaskPriority::POST_PAINT so that the flush timer runs less
277 // frequently but each pass copies a more up-to-date offscreen
278 // surface.
279 // TODO: fix tdf#163734 on macOS
280 SetPriority(TaskPriority::POST_PAINT);
281 #else
282 SetPriority(TaskPriority::HIGHEST);
283 #endif
287 SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvider* pProvider)
288 : mParent(rParent)
289 , mProvider(pProvider)
290 , mIsGPU(false)
291 , moLineColor(std::nullopt)
292 , moFillColor(std::nullopt)
293 , mXorMode(XorMode::None)
294 , mFlush(new SkiaFlushIdle(this))
295 , mScaling(1)
296 , mInWindowBackingPropertiesChanged(false)
300 SkiaSalGraphicsImpl::~SkiaSalGraphicsImpl()
302 assert(!mSurface);
303 assert(!mWindowContext);
306 void SkiaSalGraphicsImpl::createSurface()
308 SkiaZone zone;
309 if (isOffscreen())
310 createOffscreenSurface();
311 else
312 createWindowSurface();
313 mClipRegion = vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight()));
314 mDirtyRect = SkIRect::MakeWH(GetWidth(), GetHeight());
315 setCanvasScalingAndClipping();
317 // We don't want to be swapping before we've painted.
318 mFlush->Stop();
319 mFlush->SetPriority(TaskPriority::POST_PAINT);
322 void SkiaSalGraphicsImpl::createWindowSurface(bool forceRaster)
324 SkiaZone zone;
325 assert(!isOffscreen());
326 assert(!mSurface);
327 createWindowSurfaceInternal(forceRaster);
328 if (!mSurface)
330 switch (forceRaster ? RenderRaster : renderMethodToUse())
332 case RenderVulkan:
333 SAL_WARN("vcl.skia",
334 "cannot create Vulkan GPU window surface, falling back to Raster");
335 destroySurface(); // destroys also WindowContext
336 return createWindowSurface(true); // try again
337 case RenderMetal:
338 SAL_WARN("vcl.skia",
339 "cannot create Metal GPU window surface, falling back to Raster");
340 destroySurface(); // destroys also WindowContext
341 return createWindowSurface(true); // try again
342 case RenderRaster:
343 abort(); // This should not really happen, do not even try to cope with it.
346 mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
347 #ifdef DBG_UTIL
348 prefillSurface(mSurface);
349 #endif
352 bool SkiaSalGraphicsImpl::isOffscreen() const
354 if (mProvider == nullptr || mProvider->IsOffScreen())
355 return true;
356 // HACK: Sometimes (tdf#131939, tdf#138022, tdf#140288) VCL passes us a zero-sized window,
357 // and zero size is invalid for Skia, so force offscreen surface, where we handle this.
358 if (GetWidth() <= 0 || GetHeight() <= 0)
359 return true;
360 return false;
363 void SkiaSalGraphicsImpl::createOffscreenSurface()
365 SkiaZone zone;
366 assert(isOffscreen());
367 assert(!mSurface);
368 // HACK: See isOffscreen().
369 int width = std::max(1, GetWidth());
370 int height = std::max(1, GetHeight());
371 // We need to use window scaling even for offscreen surfaces, because the common usage is rendering something
372 // into an offscreen surface and then copy it to a window, so without scaling here the result would be originally
373 // drawn without scaling and only upscaled when drawing to a window.
374 mScaling = getWindowScaling();
375 mSurface = createSkSurface(width * mScaling, height * mScaling);
376 assert(mSurface);
377 mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
380 void SkiaSalGraphicsImpl::destroySurface()
382 SkiaZone zone;
383 if (mSurface)
385 // check setClipRegion() invariant
386 assert(mSurface->getCanvas()->getSaveCount() == 3);
387 // if this fails, something forgot to use SkAutoCanvasRestore
388 assert(mSurface->getCanvas()->getTotalMatrix() == SkMatrix::Scale(mScaling, mScaling));
390 mSurface.reset();
391 mWindowContext.reset();
392 mIsGPU = false;
393 mScaling = 1;
396 void SkiaSalGraphicsImpl::performFlush()
398 SkiaZone zone;
399 flushDrawing();
400 if (mSurface)
402 // Related: tdf#152703 Eliminate flickering during live resizing of a window
403 // When in live resize, the SkiaSalGraphicsImpl class does not detect that
404 // the window size has changed until after the flush has been called so
405 // call checkSurface() to recreate the SkSurface if needed before flushing.
406 checkSurface();
407 if (mDirtyRect.intersect(SkIRect::MakeWH(GetWidth(), GetHeight())))
408 flushSurfaceToWindowContext();
409 mDirtyRect.setEmpty();
413 void SkiaSalGraphicsImpl::flushSurfaceToWindowContext()
415 sk_sp<SkSurface> screenSurface = mWindowContext->getBackbufferSurface();
416 if (screenSurface != mSurface)
418 // GPU-based window contexts require calling getBackbufferSurface()
419 // for every swapBuffers(), for this reason mSurface is an offscreen surface
420 // where we keep the contents (LO does not do full redraws).
421 // So here blit the surface to the window context surface and then swap it.
423 // Raster should always draw directly to backbuffer to save copying
424 // except for small sizes - see renderMethodToUseForSize
425 assert(isGPU() || (mSurface->width() <= 32 && mSurface->height() <= 32));
426 SkPaint paint;
427 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
428 // We ignore mDirtyRect here, and mSurface already is in screenSurface coordinates,
429 // so no transformation needed.
430 screenSurface->getCanvas()->drawImage(makeCheckedImageSnapshot(mSurface), 0, 0,
431 SkSamplingOptions(), &paint);
432 // Otherwise the window is not drawn sometimes.
433 if (auto dContext = GrAsDirectContext(screenSurface->getCanvas()->recordingContext()))
434 dContext->flushAndSubmit();
435 mWindowContext->swapBuffers(nullptr); // Must swap the entire surface.
437 else
439 // For raster mode use directly the backbuffer surface, it's just a bitmap
440 // surface anyway, and for those there's no real requirement to call
441 // getBackbufferSurface() repeatedly. Using our own surface would duplicate
442 // memory and cost time copying pixels around.
443 assert(!isGPU());
444 SkIRect dirtyRect = mDirtyRect;
445 if (mScaling != 1) // Adjust to mSurface coordinates if needed.
446 dirtyRect = scaleRect(dirtyRect, mScaling);
447 mWindowContext->swapBuffers(&dirtyRect);
451 void SkiaSalGraphicsImpl::DeInit() { destroySurface(); }
453 void SkiaSalGraphicsImpl::preDraw()
455 DBG_TESTSOLARMUTEX();
456 SkiaZone::enter(); // matched in postDraw()
457 checkSurface();
458 checkPendingDrawing();
461 void SkiaSalGraphicsImpl::postDraw()
463 scheduleFlush();
464 // Skia (at least when using Vulkan) queues drawing commands and executes them only later.
465 // But tdf#136369 leads to creating and queueing many tiny bitmaps, which makes
466 // Skia slow, and may make it even run out of memory. So force a flush if such
467 // a problematic operation has been performed too many times without a flush.
468 // Note that the counter is a static variable, as all drawing shares the same Skia drawing
469 // context (and so the flush here will also flush all drawing).
470 static int maxOperationsToFlush = 1000;
471 if (pendingOperationsToFlush > maxOperationsToFlush)
473 if (auto dContext = GrAsDirectContext(mSurface->getCanvas()->recordingContext()))
474 dContext->flushAndSubmit();
475 pendingOperationsToFlush = 0;
477 SkiaZone::leave(); // matched in preDraw()
478 // If there's a problem with the GPU context, abort.
479 if (GrDirectContext* context = GrAsDirectContext(mSurface->getCanvas()->recordingContext()))
481 // We don't know the exact status of the surface (and what has or has not been drawn to it).
482 // But let's pretend it was drawn OK, and reduce the flush limit, to try to avoid possible
483 // small HW memory limitation
484 if (context->oomed())
486 if (maxOperationsToFlush > 10)
488 maxOperationsToFlush /= 2;
490 else
492 SAL_WARN("vcl.skia", "GPU context has run out of memory, aborting.");
493 abort();
496 // Unrecoverable problem.
497 if (context->abandoned())
499 SAL_WARN("vcl.skia", "GPU context has been abandoned, aborting.");
500 abort();
505 void SkiaSalGraphicsImpl::scheduleFlush()
507 if (!isOffscreen())
509 if (!Application::IsInExecute())
510 performFlush(); // otherwise nothing would trigger idle rendering
511 else if (!mFlush->IsActive())
512 mFlush->Start();
516 // VCL can sometimes resize us without telling us, update the surface if needed.
517 // Also create the surface on demand if it has not been created yet (it is a waste
518 // to create it in Init() if it gets recreated later anyway).
519 void SkiaSalGraphicsImpl::checkSurface()
521 if (!mSurface)
523 createSurface();
524 SAL_INFO("vcl.skia.trace",
525 "create(" << this << "): " << Size(mSurface->width(), mSurface->height()));
527 else if (mInWindowBackingPropertiesChanged || GetWidth() * mScaling != mSurface->width()
528 || GetHeight() * mScaling != mSurface->height())
530 if (!avoidRecreateByResize())
532 Size oldSize(mSurface->width(), mSurface->height());
533 // Recreating a surface means that the old SkSurface contents will be lost.
534 // But if a window has been resized the windowing system may send repaint events
535 // only for changed parts and VCL would not repaint the whole area, assuming
536 // that some parts have not changed (this is what seems to cause tdf#131952).
537 // So carry over the old contents for windows, even though generally everything
538 // will be usually repainted anyway.
539 sk_sp<SkImage> snapshot;
540 if (!isOffscreen())
542 flushDrawing();
543 snapshot = makeCheckedImageSnapshot(mSurface);
546 destroySurface();
547 createSurface();
549 if (snapshot)
551 SkPaint paint;
552 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
553 // Scaling by current mScaling is active, undo that. We assume that the scaling
554 // does not change.
555 resetCanvasScalingAndClipping();
556 mSurface->getCanvas()->drawImage(snapshot, 0, 0, SkSamplingOptions(), &paint);
557 setCanvasScalingAndClipping();
559 SAL_INFO("vcl.skia.trace", "recreate(" << this << "): old " << oldSize << " new "
560 << Size(mSurface->width(), mSurface->height())
561 << " requested "
562 << Size(GetWidth(), GetHeight()));
567 bool SkiaSalGraphicsImpl::avoidRecreateByResize() const
569 // Keep the old surface if VCL sends us a broken size (see isOffscreen()).
570 if (GetWidth() == 0 || GetHeight() == 0)
571 return true;
572 return false;
575 void SkiaSalGraphicsImpl::flushDrawing()
577 if (!mSurface)
578 return;
579 checkPendingDrawing();
580 ++pendingOperationsToFlush;
583 void SkiaSalGraphicsImpl::setCanvasScalingAndClipping()
585 SkCanvas* canvas = mSurface->getCanvas();
586 assert(canvas->getSaveCount() == 1);
587 // If HiDPI scaling is active, simply set a scaling matrix for the canvas. This means
588 // that all painting can use VCL coordinates and they'll be automatically translated to mSurface
589 // scaled coordinates. If that is not wanted, the scale() state needs to be temporarily unset.
590 // State such as mDirtyRect is not scaled, the scaling matrix applies to clipping too,
591 // and the rest needs to be handled explicitly.
592 // When reading mSurface contents there's no automatic scaling and it needs to be handled explicitly.
593 canvas->save(); // keep the original state without any scaling
594 canvas->scale(mScaling, mScaling);
596 // SkCanvas::clipRegion() can only further reduce the clip region,
597 // but we need to set the given region, which may extend it.
598 // So handle that by always having the full clip region saved on the stack
599 // and always go back to that. SkCanvas::restore() only affects the clip
600 // and the matrix.
601 canvas->save(); // keep scaled state without clipping
602 setCanvasClipRegion(canvas, mClipRegion);
605 void SkiaSalGraphicsImpl::resetCanvasScalingAndClipping()
607 SkCanvas* canvas = mSurface->getCanvas();
608 assert(canvas->getSaveCount() == 3);
609 canvas->restore(); // undo clipping
610 canvas->restore(); // undo scaling
613 void SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
615 if (mClipRegion == region)
616 return;
617 SkiaZone zone;
618 checkPendingDrawing();
619 checkSurface();
620 mClipRegion = region;
621 SAL_INFO("vcl.skia.trace", "setclipregion(" << this << "): " << region);
622 SkCanvas* canvas = mSurface->getCanvas();
623 assert(canvas->getSaveCount() == 3);
624 canvas->restore(); // undo previous clip state, see setCanvasScalingAndClipping()
625 canvas->save();
626 setCanvasClipRegion(canvas, region);
629 void SkiaSalGraphicsImpl::setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region)
631 SkiaZone zone;
632 SkPath path;
633 // Always use region rectangles, regardless of what the region uses internally.
634 // That's what other VCL backends do, and trying to use addPolyPolygonToPath()
635 // in case a polygon is used leads to off-by-one errors such as tdf#133208.
636 RectangleVector rectangles;
637 region.GetRegionRectangles(rectangles);
638 path.incReserve(rectangles.size() + 1);
639 for (const tools::Rectangle& rectangle : rectangles)
640 path.addRect(SkRect::MakeXYWH(rectangle.getX(), rectangle.getY(), rectangle.GetWidth(),
641 rectangle.GetHeight()));
642 path.setFillType(SkPathFillType::kEvenOdd);
643 canvas->clipPath(path);
646 void SkiaSalGraphicsImpl::ResetClipRegion()
648 setClipRegion(vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight())));
651 const vcl::Region& SkiaSalGraphicsImpl::getClipRegion() const { return mClipRegion; }
653 sal_uInt16 SkiaSalGraphicsImpl::GetBitCount() const { return 32; }
655 tools::Long SkiaSalGraphicsImpl::GetGraphicsWidth() const { return GetWidth(); }
657 void SkiaSalGraphicsImpl::SetLineColor()
659 checkPendingDrawing();
660 moLineColor = std::nullopt;
663 void SkiaSalGraphicsImpl::SetLineColor(Color nColor)
665 checkPendingDrawing();
666 moLineColor = nColor;
669 void SkiaSalGraphicsImpl::SetFillColor()
671 checkPendingDrawing();
672 moFillColor = std::nullopt;
675 void SkiaSalGraphicsImpl::SetFillColor(Color nColor)
677 checkPendingDrawing();
678 moFillColor = nColor;
681 void SkiaSalGraphicsImpl::SetXORMode(bool set, bool invert)
683 XorMode newMode = set ? (invert ? XorMode::Invert : XorMode::Xor) : XorMode::None;
684 if (newMode == mXorMode)
685 return;
686 checkPendingDrawing();
687 SAL_INFO("vcl.skia.trace", "setxormode(" << this << "): " << set << "/" << invert);
688 mXorMode = newMode;
691 void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor)
693 checkPendingDrawing();
694 switch (nROPColor)
696 case SalROPColor::N0:
697 moLineColor = Color(0, 0, 0);
698 break;
699 case SalROPColor::N1:
700 case SalROPColor::Invert:
701 moLineColor = Color(0xff, 0xff, 0xff);
702 break;
706 void SkiaSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor)
708 checkPendingDrawing();
709 switch (nROPColor)
711 case SalROPColor::N0:
712 moFillColor = Color(0, 0, 0);
713 break;
714 case SalROPColor::N1:
715 case SalROPColor::Invert:
716 moFillColor = Color(0xff, 0xff, 0xff);
717 break;
721 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY)
723 drawPixel(nX, nY, *moLineColor);
726 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
728 preDraw();
729 SAL_INFO("vcl.skia.trace", "drawpixel(" << this << "): " << Point(nX, nY) << ":" << nColor);
730 addUpdateRegion(SkRect::MakeXYWH(nX, nY, 1, 1));
731 SkPaint paint = makePixelPaint(nColor);
732 // Apparently drawPixel() is actually expected to set the pixel and not draw it.
733 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
735 #ifdef MACOSX
736 // tdf#148569 set extra drawing constraints when scaling
737 // Previously, setting stroke width and cap was only done when running
738 // unit tests. But the same drawing constraints are necessary when running
739 // with a Retina display on macOS.
740 if (mScaling != 1)
741 #else
742 // Related tdf#148569: do not apply macOS fix to non-macOS platforms
743 // Setting the stroke width and cap has a noticeable performance penalty
744 // when running on GTK3. Since tdf#148569 only appears to occur on macOS
745 // Retina displays, revert commit a4488013ee6c87a97501b620dbbf56622fb70246
746 // for non-macOS platforms.
747 if (mScaling != 1 && isUnitTestRunning())
748 #endif
750 // On HiDPI displays, draw a square on the entire non-hidpi "pixel" when running unittests,
751 // since tests often require precise pixel drawing.
752 paint.setStrokeWidth(1); // this will be scaled by mScaling
753 paint.setStrokeCap(SkPaint::kSquare_Cap);
755 getDrawCanvas()->drawPoint(toSkX(nX), toSkY(nY), paint);
756 postDraw();
759 void SkiaSalGraphicsImpl::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
760 tools::Long nY2)
762 if (!moLineColor)
763 return;
764 preDraw();
765 SAL_INFO("vcl.skia.trace", "drawline(" << this << "): " << Point(nX1, nY1) << "->"
766 << Point(nX2, nY2) << ":" << *moLineColor);
767 addUpdateRegion(SkRect::MakeLTRB(nX1, nY1, nX2, nY2).makeSorted());
768 SkPaint paint = makeLinePaint();
769 paint.setAntiAlias(mParent.getAntiAlias());
770 if (mScaling != 1 && isUnitTestRunning())
772 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
773 // smoothing that would confuse unittests.
774 paint.setStrokeWidth(1); // this will be scaled by mScaling
775 paint.setStrokeCap(SkPaint::kSquare_Cap);
777 getDrawCanvas()->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
778 postDraw();
781 void SkiaSalGraphicsImpl::privateDrawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
782 tools::Long nHeight, double fTransparency,
783 bool blockAA)
785 preDraw();
786 SAL_INFO("vcl.skia.trace", "privatedrawrect("
787 << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight)
788 << ":" << dumpOptionalColor(moLineColor) << ":"
789 << dumpOptionalColor(moFillColor) << ":" << fTransparency);
790 addUpdateRegion(SkRect::MakeXYWH(nX, nY, nWidth, nHeight));
791 SkCanvas* canvas = getDrawCanvas();
792 if (moFillColor)
794 SkPaint paint = makeFillPaint(fTransparency);
795 paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
796 // HACK: If the polygon is just a line, it still should be drawn. But when filling
797 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
798 if (!moLineColor && SkSize::Make(nWidth, nHeight).isEmpty())
799 paint.setStyle(SkPaint::kStroke_Style);
800 canvas->drawIRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), paint);
802 if (moLineColor && moLineColor != moFillColor) // otherwise handled by fill
804 SkPaint paint = makeLinePaint(fTransparency);
805 paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
806 #ifdef MACOSX
807 // tdf#162646 set extra drawing constraints when scaling
808 // Previously, setting stroke width and cap was only done when running
809 // unit tests. But the same drawing constraints are necessary when
810 // running with a Retina display on macOS and antialiasing is disabled.
811 if (mScaling != 1 && (isUnitTestRunning() || !paint.isAntiAlias()))
812 #else
813 if (mScaling != 1 && isUnitTestRunning())
814 #endif
816 // On HiDPI displays, do not draw just a hairline but instead a full-width "pixel" when running unittests,
817 // since tests often require precise pixel drawing.
818 paint.setStrokeWidth(1); // this will be scaled by mScaling
819 paint.setStrokeCap(SkPaint::kSquare_Cap);
821 // The obnoxious "-1 DrawRect()" hack that I don't understand the purpose of (and I'm not sure
822 // if anybody does), but without it some cases do not work. The max() is needed because Skia
823 // will not draw anything if width or height is 0.
824 canvas->drawRect(SkRect::MakeXYWH(toSkX(nX), toSkY(nY),
825 std::max(tools::Long(1), nWidth - 1),
826 std::max(tools::Long(1), nHeight - 1)),
827 paint);
829 postDraw();
832 void SkiaSalGraphicsImpl::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
833 tools::Long nHeight)
835 privateDrawAlphaRect(nX, nY, nWidth, nHeight, 0.0, true);
838 void SkiaSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
840 basegfx::B2DPolygon aPolygon;
841 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
842 for (sal_uInt32 i = 1; i < nPoints; ++i)
843 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
844 aPolygon.setClosed(false);
846 drawPolyLine(basegfx::B2DHomMatrix(), aPolygon, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
847 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false);
850 void SkiaSalGraphicsImpl::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
852 basegfx::B2DPolygon aPolygon;
853 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
854 for (sal_uInt32 i = 1; i < nPoints; ++i)
855 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
857 drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPolygon), 0.0);
860 void SkiaSalGraphicsImpl::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
861 const Point** pPtAry)
863 basegfx::B2DPolyPolygon aPolyPolygon;
864 for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
866 sal_uInt32 nPoints = pPoints[nPolygon];
867 if (nPoints)
869 const Point* pSubPoints = pPtAry[nPolygon];
870 basegfx::B2DPolygon aPolygon;
871 aPolygon.append(basegfx::B2DPoint(pSubPoints->getX(), pSubPoints->getY()), nPoints);
872 for (sal_uInt32 i = 1; i < nPoints; ++i)
873 aPolygon.setB2DPoint(i,
874 basegfx::B2DPoint(pSubPoints[i].getX(), pSubPoints[i].getY()));
876 aPolyPolygon.append(aPolygon);
880 drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon, 0.0);
883 void SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
884 const basegfx::B2DPolyPolygon& rPolyPolygon,
885 double fTransparency)
887 const bool bHasFill(moFillColor.has_value());
888 const bool bHasLine(moLineColor.has_value());
890 if (rPolyPolygon.count() == 0 || !(bHasFill || bHasLine) || fTransparency < 0.0
891 || fTransparency >= 1.0)
892 return;
894 basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
895 aPolyPolygon.transform(rObjectToDevice);
897 SAL_INFO("vcl.skia.trace", "drawpolypolygon(" << this << "): " << aPolyPolygon << ":"
898 << dumpOptionalColor(moLineColor) << ":"
899 << dumpOptionalColor(moFillColor));
901 if (delayDrawPolyPolygon(aPolyPolygon, fTransparency))
903 scheduleFlush();
904 return;
907 performDrawPolyPolygon(aPolyPolygon, fTransparency, mParent.getAntiAlias());
910 void SkiaSalGraphicsImpl::performDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
911 double fTransparency, bool useAA)
913 preDraw();
915 SkPath polygonPath;
916 bool hasOnlyOrthogonal = true;
917 addPolyPolygonToPath(aPolyPolygon, polygonPath, &hasOnlyOrthogonal);
918 polygonPath.setFillType(SkPathFillType::kEvenOdd);
919 addUpdateRegion(polygonPath.getBounds());
921 // For lines we use toSkX()/toSkY() in order to pass centers of pixels to Skia,
922 // as that leads to better results with floating-point coordinates
923 // (e.g. https://bugs.chromium.org/p/skia/issues/detail?id=9611).
924 // But that means that we generally need to use it also for areas, so that they
925 // line up properly if used together (tdf#134346).
926 // On the other hand, with AA enabled and rectangular areas, this leads to fuzzy
927 // edges (tdf#137329). But since rectangular areas line up perfectly to pixels
928 // everywhere, it shouldn't be necessary to do this for them.
929 // So if AA is enabled, avoid this fixup for rectangular areas.
930 if (!useAA || !hasOnlyOrthogonal)
932 #ifdef MACOSX
933 // tdf#162646 don't move orthogonal polypolygons when scaling
934 // Previously, polypolygons would be moved slightly but this causes
935 // misdrawing of orthogonal polypolygons (i.e. polypolygons with only
936 // vertical and horizontal lines) when using a Retina display on
937 // macOS and antialiasing is disabled.
938 if ((!isUnitTestRunning() && (useAA || !hasOnlyOrthogonal)) || getWindowScaling() == 1)
939 #else
940 // We normally use pixel at their center positions, but slightly off (see toSkX/Y()).
941 // With AA lines that "slightly off" causes tiny changes of color, making some tests
942 // fail. Since moving AA-ed line slightly to a side doesn't cause any real visual
943 // difference, just place exactly at the center. tdf#134346
944 // When running on macOS with a Retina display, one BackendTest unit
945 // test will fail if the position is adjusted.
946 if (!isUnitTestRunning() || getWindowScaling() == 1)
947 #endif
949 const SkScalar posFix = useAA ? toSkXYFix : 0;
950 polygonPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
953 if (moFillColor)
955 SkPaint aPaint = makeFillPaint(fTransparency);
956 aPaint.setAntiAlias(useAA);
957 // HACK: If the polygon is just a line, it still should be drawn. But when filling
958 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
959 if (!moLineColor && polygonPath.getBounds().isEmpty())
960 aPaint.setStyle(SkPaint::kStroke_Style);
961 getDrawCanvas()->drawPath(polygonPath, aPaint);
963 if (moLineColor && moLineColor != moFillColor) // otherwise handled by fill
965 SkPaint aPaint = makeLinePaint(fTransparency);
966 aPaint.setAntiAlias(useAA);
967 getDrawCanvas()->drawPath(polygonPath, aPaint);
969 postDraw();
972 namespace
974 struct LessThan
976 bool operator()(const basegfx::B2DPoint& point1, const basegfx::B2DPoint& point2) const
978 if (basegfx::fTools::equal(point1.getX(), point2.getX()))
979 return basegfx::fTools::less(point1.getY(), point2.getY());
980 return basegfx::fTools::less(point1.getX(), point2.getX());
983 } // namespace
985 bool SkiaSalGraphicsImpl::delayDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
986 double fTransparency)
988 // There is some code that needlessly subdivides areas into adjacent rectangles,
989 // but Skia doesn't line them up perfectly if AA is enabled (e.g. Cairo, Qt5 do,
990 // but Skia devs claim it's working as intended
991 // https://groups.google.com/d/msg/skia-discuss/NlKpD2X_5uc/Vuwd-kyYBwAJ).
992 // An example is tdf#133016, which triggers SvgStyleAttributes::add_stroke()
993 // implementing a line stroke as a bunch of polygons instead of just one, and
994 // SvgLinearAtomPrimitive2D::create2DDecomposition() creates a gradient
995 // as a series of polygons of gradually changing color. Those places should be
996 // changed, but try to merge those split polygons back into the original one,
997 // where the needlessly created edges causing problems will not exist.
998 // This means drawing of such polygons needs to be delayed, so that they can
999 // be possibly merged with the next one.
1000 // Merge only polygons of the same properties (color, etc.), so the gradient problem
1001 // actually isn't handled here.
1003 // Only AA polygons need merging, because they do not line up well because of the AA of the edges.
1004 if (!mParent.getAntiAlias())
1005 return false;
1006 // Only filled polygons without an outline are problematic.
1007 if (!moFillColor || moLineColor)
1008 return false;
1009 // Merge only simple polygons, real polypolygons most likely aren't needlessly split,
1010 // so they do not need joining.
1011 if (aPolyPolygon.count() != 1)
1012 return false;
1013 // If the polygon is not closed, it doesn't mark an area to be filled.
1014 if (!aPolyPolygon.isClosed())
1015 return false;
1016 // If a polygon does not contain a straight line, i.e. it's all curves, then do not merge.
1017 // First of all that's even more expensive, and second it's very unlikely that it's a polygon
1018 // split into more polygons.
1019 if (!polygonContainsLine(aPolyPolygon))
1020 return false;
1022 if (!mLastPolyPolygonInfo.polygons.empty()
1023 && (mLastPolyPolygonInfo.transparency != fTransparency
1024 || !mLastPolyPolygonInfo.bounds.overlaps(aPolyPolygon.getB2DRange())))
1026 checkPendingDrawing(); // Cannot be parts of the same larger polygon, draw the last and reset.
1028 if (!mLastPolyPolygonInfo.polygons.empty())
1030 assert(aPolyPolygon.count() == 1);
1031 assert(mLastPolyPolygonInfo.polygons.back().count() == 1);
1032 // Check if the new and the previous polygon share at least one point. If not, then they
1033 // cannot be adjacent polygons, so there's no point in trying to merge them.
1034 bool sharePoint = false;
1035 const basegfx::B2DPolygon& poly1 = aPolyPolygon.getB2DPolygon(0);
1036 const basegfx::B2DPolygon& poly2 = mLastPolyPolygonInfo.polygons.back().getB2DPolygon(0);
1037 o3tl::sorted_vector<basegfx::B2DPoint, LessThan> poly1Points; // for O(n log n)
1038 poly1Points.reserve(poly1.count());
1039 for (sal_uInt32 i = 0; i < poly1.count(); ++i)
1040 poly1Points.insert(poly1.getB2DPoint(i));
1041 for (sal_uInt32 i = 0; i < poly2.count(); ++i)
1042 if (poly1Points.find(poly2.getB2DPoint(i)) != poly1Points.end())
1044 sharePoint = true;
1045 break;
1047 if (!sharePoint)
1048 checkPendingDrawing(); // Draw the previous one and reset.
1050 // Collect the polygons that can be possibly merged. Do the merging only once at the end,
1051 // because it's not a cheap operation.
1052 mLastPolyPolygonInfo.polygons.push_back(aPolyPolygon);
1053 mLastPolyPolygonInfo.bounds.expand(aPolyPolygon.getB2DRange());
1054 mLastPolyPolygonInfo.transparency = fTransparency;
1055 return true;
1058 // Tdf#140848 - basegfx::utils::mergeToSinglePolyPolygon() seems to have rounding
1059 // errors that sometimes cause it to merge incorrectly.
1060 static void roundPolygonPoints(basegfx::B2DPolyPolygon& polyPolygon)
1062 for (basegfx::B2DPolygon& polygon : polyPolygon)
1064 polygon.makeUnique();
1065 for (sal_uInt32 i = 0; i < polygon.count(); ++i)
1066 polygon.setB2DPoint(i, basegfx::B2DPoint(basegfx::fround(polygon.getB2DPoint(i))));
1067 // Control points are saved as vectors relative to points, so hopefully
1068 // there's no need to round those.
1072 void SkiaSalGraphicsImpl::checkPendingDrawing()
1074 if (!mLastPolyPolygonInfo.polygons.empty())
1075 { // Flush any pending polygon drawing.
1076 basegfx::B2DPolyPolygonVector polygons;
1077 std::swap(polygons, mLastPolyPolygonInfo.polygons);
1078 double transparency = mLastPolyPolygonInfo.transparency;
1079 mLastPolyPolygonInfo.bounds.reset();
1080 if (polygons.size() == 1)
1081 performDrawPolyPolygon(polygons.front(), transparency, true);
1082 else
1084 for (basegfx::B2DPolyPolygon& p : polygons)
1085 roundPolygonPoints(p);
1086 performDrawPolyPolygon(basegfx::utils::mergeToSinglePolyPolygon(polygons), transparency,
1087 true);
1092 bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
1093 const basegfx::B2DPolygon& rPolyLine, double fTransparency,
1094 double fLineWidth, const std::vector<double>* pStroke,
1095 basegfx::B2DLineJoin eLineJoin,
1096 css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
1097 bool bPixelSnapHairline)
1099 if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0 || !moLineColor)
1101 return true;
1104 preDraw();
1105 SAL_INFO("vcl.skia.trace",
1106 "drawpolyline(" << this << "): " << rPolyLine << ":" << *moLineColor);
1108 // Adjust line width for object-to-device scale.
1109 fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
1110 #ifdef MACOSX
1111 // tdf#162646 suppressing drawing hairlines when scaling
1112 // Previously, drawing of hairlines (i.e. zero line width) was only
1113 // suppressed when running unit tests. But drawing hairlines causes
1114 // unexpected shifting of the lines when using a Retina display on
1115 // macOS and antialiasing is disabled.
1116 if (fLineWidth == 0 && mScaling != 1 && (isUnitTestRunning() || !mParent.getAntiAlias()))
1117 #else
1118 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
1119 // smoothing that would confuse unittests.
1120 if (fLineWidth == 0 && mScaling != 1 && isUnitTestRunning())
1121 #endif
1122 fLineWidth = 1; // this will be scaled by mScaling
1124 // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
1125 basegfx::B2DPolygon aPolyLine(rPolyLine);
1126 aPolyLine.transform(rObjectToDevice);
1127 if (bPixelSnapHairline)
1129 aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);
1132 SkPaint aPaint = makeLinePaint(fTransparency);
1134 switch (eLineJoin)
1136 case basegfx::B2DLineJoin::Bevel:
1137 aPaint.setStrokeJoin(SkPaint::kBevel_Join);
1138 break;
1139 case basegfx::B2DLineJoin::Round:
1140 aPaint.setStrokeJoin(SkPaint::kRound_Join);
1141 break;
1142 case basegfx::B2DLineJoin::NONE:
1143 break;
1144 case basegfx::B2DLineJoin::Miter:
1145 aPaint.setStrokeJoin(SkPaint::kMiter_Join);
1146 // convert miter minimum angle to miter limit
1147 aPaint.setStrokeMiter(1.0 / std::sin(fMiterMinimumAngle / 2.0));
1148 break;
1151 switch (eLineCap)
1153 case css::drawing::LineCap_ROUND:
1154 aPaint.setStrokeCap(SkPaint::kRound_Cap);
1155 break;
1156 case css::drawing::LineCap_SQUARE:
1157 aPaint.setStrokeCap(SkPaint::kSquare_Cap);
1158 break;
1159 default: // css::drawing::LineCap_BUTT:
1160 aPaint.setStrokeCap(SkPaint::kButt_Cap);
1161 break;
1164 aPaint.setStrokeWidth(fLineWidth);
1165 aPaint.setAntiAlias(mParent.getAntiAlias());
1166 // See the tdf#134346 comment above.
1167 const SkScalar posFix = mParent.getAntiAlias() ? toSkXYFix : 0;
1169 if (pStroke && std::accumulate(pStroke->begin(), pStroke->end(), 0.0) != 0)
1171 std::vector<SkScalar> intervals;
1172 // Transform size by the matrix.
1173 for (double stroke : *pStroke)
1174 intervals.push_back((rObjectToDevice * basegfx::B2DVector(stroke, 0)).getLength());
1175 aPaint.setPathEffect(SkDashPathEffect::Make(intervals.data(), intervals.size(), 0));
1178 // Skia does not support basegfx::B2DLineJoin::NONE, so in that case batch only if lines
1179 // are not wider than a pixel.
1180 if (eLineJoin != basegfx::B2DLineJoin::NONE || fLineWidth <= 1.0)
1182 SkPath aPath;
1183 aPath.incReserve(aPolyLine.count() * 3); // because cubicTo is 3 elements
1184 addPolygonToPath(aPolyLine, aPath);
1185 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1186 addUpdateRegion(aPath.getBounds());
1187 getDrawCanvas()->drawPath(aPath, aPaint);
1189 else
1191 sal_uInt32 nPoints = aPolyLine.count();
1192 bool bClosed = aPolyLine.isClosed();
1193 bool bHasCurves = aPolyLine.areControlPointsUsed();
1194 for (sal_uInt32 j = 0; j < nPoints; ++j)
1196 SkPath aPath;
1197 aPath.incReserve(2 * 3); // because cubicTo is 3 elements
1198 addPolygonToPath(aPolyLine, aPath, j, j + 1, nPoints, bClosed, bHasCurves);
1199 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1200 addUpdateRegion(aPath.getBounds());
1201 getDrawCanvas()->drawPath(aPath, aPaint);
1205 postDraw();
1207 return true;
1210 bool SkiaSalGraphicsImpl::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*)
1212 return false;
1215 bool SkiaSalGraphicsImpl::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*)
1217 return false;
1220 bool SkiaSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*, const Point* const*,
1221 const PolyFlags* const*)
1223 return false;
1226 void SkiaSalGraphicsImpl::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
1227 tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
1228 bool /*bWindowInvalidate*/)
1230 if (nDestX == nSrcX && nDestY == nSrcY)
1231 return;
1232 preDraw();
1233 SAL_INFO("vcl.skia.trace", "copyarea("
1234 << this << "): " << Point(nSrcX, nSrcY) << "->"
1235 << SkIRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
1236 // Using SkSurface::draw() should be more efficient, but it's too buggy.
1237 SalTwoRect rPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
1238 privateCopyBits(rPosAry, this);
1239 postDraw();
1242 void SkiaSalGraphicsImpl::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
1244 preDraw();
1245 SkiaSalGraphicsImpl* src;
1246 if (pSrcGraphics)
1248 assert(dynamic_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl()));
1249 src = static_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl());
1250 src->checkSurface();
1251 src->flushDrawing();
1253 else
1255 src = this;
1256 assert(mXorMode == XorMode::None);
1258 auto srcDebug = [&]() -> std::string {
1259 if (src == this)
1260 return "(self)";
1261 else
1263 std::ostringstream stream;
1264 stream << "(" << src << ")";
1265 return stream.str();
1268 SAL_INFO("vcl.skia.trace", "copybits(" << this << "): " << srcDebug() << ": " << rPosAry);
1269 privateCopyBits(rPosAry, src);
1270 postDraw();
1273 void SkiaSalGraphicsImpl::privateCopyBits(const SalTwoRect& rPosAry, SkiaSalGraphicsImpl* src)
1275 assert(mXorMode == XorMode::None);
1276 addUpdateRegion(SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1277 rPosAry.mnDestHeight));
1278 SkPaint paint;
1279 paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
1280 SkIRect srcRect = SkIRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth,
1281 rPosAry.mnSrcHeight);
1282 SkRect destRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1283 rPosAry.mnDestHeight);
1285 if (!SkIRect::Intersects(srcRect, SkIRect::MakeWH(src->GetWidth(), src->GetHeight()))
1286 || !SkRect::Intersects(destRect, SkRect::MakeWH(GetWidth(), GetHeight())))
1287 return;
1289 if (src == this)
1291 // Copy-to-self means that we'd take a snapshot, which would refcount the data,
1292 // and then drawing would result in copy in write, copying the entire surface.
1293 // Try to copy less by making a snapshot of only what is needed.
1294 // A complication here is that drawImageRect() can handle coordinates outside
1295 // of surface fine, but makeImageSnapshot() will crop to the surface area,
1296 // so do that manually here in order to adjust also destination rectangle.
1297 if (srcRect.x() < 0 || srcRect.y() < 0)
1299 destRect.fLeft += -srcRect.x();
1300 destRect.fTop += -srcRect.y();
1301 srcRect.adjust(-srcRect.x(), -srcRect.y(), 0, 0);
1303 // Note that right() and bottom() are not inclusive (are outside of the rect).
1304 if (srcRect.right() - 1 > GetWidth() || srcRect.bottom() - 1 > GetHeight())
1306 destRect.fRight += GetWidth() - srcRect.right();
1307 destRect.fBottom += GetHeight() - srcRect.bottom();
1308 srcRect.adjust(0, 0, GetWidth() - srcRect.right(), GetHeight() - srcRect.bottom());
1310 // Scaling for source coordinates must be done manually.
1311 if (src->mScaling != 1)
1312 srcRect = scaleRect(srcRect, src->mScaling);
1313 sk_sp<SkImage> image = makeCheckedImageSnapshot(src->mSurface, srcRect);
1314 srcRect.offset(-srcRect.x(), -srcRect.y());
1315 getDrawCanvas()->drawImageRect(image, SkRect::Make(srcRect), destRect,
1316 makeSamplingOptions(rPosAry, mScaling, src->mScaling),
1317 &paint, SkCanvas::kFast_SrcRectConstraint);
1319 else
1321 // Scaling for source coordinates must be done manually.
1322 if (src->mScaling != 1)
1323 srcRect = scaleRect(srcRect, src->mScaling);
1324 // Do not use makeImageSnapshot(rect), as that one may make a needless data copy.
1325 getDrawCanvas()->drawImageRect(makeCheckedImageSnapshot(src->mSurface),
1326 SkRect::Make(srcRect), destRect,
1327 makeSamplingOptions(rPosAry, mScaling, src->mScaling),
1328 &paint, SkCanvas::kFast_SrcRectConstraint);
1332 bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect& rPosAry, const SalBitmap& rBitmap)
1334 if (checkInvalidSourceOrDestination(rPosAry))
1335 return false;
1337 assert(dynamic_cast<const SkiaSalBitmap*>(&rBitmap));
1338 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rBitmap);
1339 // This is used by VirtualDevice in the alpha mode for the "alpha" layer
1340 // So the result is transparent only if both the inputs
1341 // are transparent. Which seems to be what SkBlendMode::kModulate does,
1342 // so use that.
1343 // See also blendAlphaBitmap().
1344 if (rSkiaBitmap.IsFullyOpaqueAsAlpha())
1346 // Optimization. If the bitmap means fully opaque, it's all one's. In CPU
1347 // mode it should be faster to just copy instead of SkBlendMode::kMultiply.
1348 drawBitmap(rPosAry, rSkiaBitmap);
1350 else
1351 drawBitmap(rPosAry, rSkiaBitmap, SkBlendMode::kModulate);
1352 return true;
1355 bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry,
1356 const SalBitmap& rSourceBitmap,
1357 const SalBitmap& rMaskBitmap,
1358 const SalBitmap& rAlphaBitmap)
1360 // tdf#156361 use slow blending path if alpha mask blending is disabled
1361 // SkiaSalGraphicsImpl::blendBitmap() fails unexpectedly in the following
1362 // cases so return false and use the non-Skia alpha mask blending code:
1363 // - Unexpected white areas when running a slideshow or printing:
1364 // https://bugs.documentfoundation.org/attachment.cgi?id=188447
1365 // - Unexpected scaling of bitmap and/or alpha mask when exporting to PDF:
1366 // https://bugs.documentfoundation.org/attachment.cgi?id=188498
1367 if (!SkiaHelper::isAlphaMaskBlendingEnabled())
1368 return false;
1370 if (checkInvalidSourceOrDestination(rPosAry))
1371 return false;
1373 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1374 assert(dynamic_cast<const SkiaSalBitmap*>(&rMaskBitmap));
1375 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1376 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1377 const SkiaSalBitmap& rSkiaMaskBitmap = static_cast<const SkiaSalBitmap&>(rMaskBitmap);
1378 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1380 if (rSkiaMaskBitmap.IsFullyOpaqueAsAlpha())
1382 // Optimization. If the mask of the bitmap to be blended means it's actually opaque,
1383 // just draw the bitmap directly (that's what the math below will result in).
1384 drawBitmap(rPosAry, rSkiaSourceBitmap);
1385 return true;
1387 // This was originally implemented for the OpenGL drawing method and it is poorly documented.
1388 // The source and mask bitmaps are the usual data and alpha bitmaps, and 'alpha'
1389 // is the "alpha" layer of the VirtualDevice (the alpha in VirtualDevice is also stored
1390 // as a separate bitmap). Now if I understand it correctly these two alpha masks first need
1391 // to be combined into the actual alpha mask to be used. The formula for TYPE_BLEND
1392 // in opengl's combinedTextureFragmentShader.glsl is
1393 // "result_alpha = 1.0 - (1.0 - floor(alpha)) * mask".
1394 // See also blendBitmap().
1396 SkSamplingOptions samplingOptions = makeSamplingOptions(rPosAry, mScaling);
1397 // First do the "( 1 - alpha ) * mask"
1398 // (no idea how to do "floor", but hopefully not needed in practice).
1399 sk_sp<SkShader> shaderAlpha
1400 = SkShaders::Blend(SkBlendMode::kDstIn, rSkiaMaskBitmap.GetAlphaSkShader(samplingOptions),
1401 rSkiaAlphaBitmap.GetAlphaSkShader(samplingOptions));
1402 // And now draw the bitmap with "1 - x", where x is the "( 1 - alpha ) * mask".
1403 sk_sp<SkShader> shader = SkShaders::Blend(SkBlendMode::kSrcIn, shaderAlpha,
1404 rSkiaSourceBitmap.GetSkShader(samplingOptions));
1405 drawShader(rPosAry, shader);
1406 return true;
1409 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
1411 if (checkInvalidSourceOrDestination(rPosAry))
1412 return;
1414 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1415 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1417 drawBitmap(rPosAry, rSkiaSourceBitmap);
1420 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1421 const SalBitmap& rMaskBitmap)
1423 drawAlphaBitmap(rPosAry, rSalBitmap, rMaskBitmap);
1426 void SkiaSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1427 Color nMaskColor)
1429 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1430 const SkiaSalBitmap& skiaBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1431 // SkBlendMode::kDstOut must be used instead of SkBlendMode::kDstIn because
1432 // the alpha channel of what is drawn appears to get inverted at some point
1433 // after it is drawn
1434 drawShader(
1435 rPosAry,
1436 SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is alpha.
1437 SkShaders::Color(toSkColor(nMaskColor)),
1438 skiaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
1441 std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(tools::Long nX, tools::Long nY,
1442 tools::Long nWidth, tools::Long nHeight)
1444 SkiaZone zone;
1445 checkSurface();
1446 SAL_INFO("vcl.skia.trace",
1447 "getbitmap(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight));
1448 flushDrawing();
1449 // TODO makeImageSnapshot(rect) may copy the data, which may be a waste if this is used
1450 // e.g. for VirtualDevice's lame alpha blending, in which case the image will eventually end up
1451 // in blendAlphaBitmap(), where we could simply use the proper rect of the image.
1452 sk_sp<SkImage> image = makeCheckedImageSnapshot(
1453 mSurface, scaleRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), mScaling));
1454 std::shared_ptr<SkiaSalBitmap> bitmap = std::make_shared<SkiaSalBitmap>(image);
1455 // If the surface is scaled for HiDPI, the bitmap needs to be scaled down, otherwise
1456 // it would have incorrect size from the API point of view. The DirectImage::Yes handling
1457 // in mergeCacheBitmaps() should access the original unscaled bitmap data to avoid
1458 // pointless scaling back and forth.
1459 if (mScaling != 1)
1461 if (!isUnitTestRunning())
1462 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, goodScalingQuality());
1463 else
1465 // Some tests require exact pixel values and would be confused by smooth-scaling.
1466 // And some draw something smooth and not smooth-scaling there would break the checks.
1467 // When running on macOS with a Retina display, several BackendTest unit tests
1468 // also need a lower quality scaling level.
1469 if (getWindowScaling() != 1
1470 || isUnitTestRunning("BackendTest__testDrawHaflEllipseAAWithPolyLineB2D_")
1471 || isUnitTestRunning("BackendTest__testDrawRectAAWithLine_")
1472 || isUnitTestRunning("GraphicsRenderTest__testDrawRectAAWithLine"))
1474 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, goodScalingQuality());
1476 else
1477 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::NearestNeighbor);
1480 return bitmap;
1483 Color SkiaSalGraphicsImpl::getPixel(tools::Long nX, tools::Long nY)
1485 SkiaZone zone;
1486 checkSurface();
1487 SAL_INFO("vcl.skia.trace", "getpixel(" << this << "): " << Point(nX, nY));
1488 flushDrawing();
1489 // This is presumably slow, but getPixel() should be generally used only by unit tests.
1490 SkBitmap bitmap;
1491 if (!bitmap.tryAllocN32Pixels(mSurface->width(), mSurface->height()))
1492 abort();
1493 if (!mSurface->readPixels(bitmap, 0, 0))
1494 abort();
1495 return fromSkColor(bitmap.getColor(nX * mScaling, nY * mScaling));
1498 void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFlags)
1500 preDraw();
1501 SAL_INFO("vcl.skia.trace", "invert(" << this << "): " << rPoly << ":" << int(eFlags));
1502 assert(mXorMode == XorMode::None);
1503 SkPath aPath;
1504 aPath.incReserve(rPoly.count());
1505 addPolygonToPath(rPoly, aPath);
1506 aPath.setFillType(SkPathFillType::kEvenOdd);
1507 addUpdateRegion(aPath.getBounds());
1509 SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
1510 SkPaint aPaint;
1511 // There's no blend mode for inverting as such, but kExclusion is 's + d - 2*s*d',
1512 // so with d = 1.0 (all channels) it becomes effectively '1 - s', i.e. inverted color.
1513 aPaint.setBlendMode(SkBlendMode::kExclusion);
1514 aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
1515 // TrackFrame just inverts a dashed path around the polygon
1516 if (eFlags == SalInvert::TrackFrame)
1518 // TrackFrame is not supposed to paint outside of the polygon (usually rectangle),
1519 // but wider stroke width usually results in that, so ensure the requirement
1520 // by clipping.
1521 getDrawCanvas()->clipRect(aPath.getBounds(), SkClipOp::kIntersect, false);
1522 aPaint.setStrokeWidth(2);
1523 static constexpr float intervals[] = { 4.0f, 4.0f };
1524 aPaint.setStyle(SkPaint::kStroke_Style);
1525 aPaint.setPathEffect(SkDashPathEffect::Make(intervals, std::size(intervals), 0));
1527 else
1529 aPaint.setStyle(SkPaint::kFill_Style);
1531 // N50 inverts in checker pattern
1532 if (eFlags == SalInvert::N50)
1534 // This creates 2x2 checker pattern bitmap
1535 // TODO Use createSkSurface() and cache the image
1536 SkBitmap aBitmap;
1537 aBitmap.allocN32Pixels(2, 2);
1538 const SkPMColor white = SkPreMultiplyARGB(0xFF, 0xFF, 0xFF, 0xFF);
1539 const SkPMColor black = SkPreMultiplyARGB(0xFF, 0x00, 0x00, 0x00);
1540 SkPMColor* scanline;
1541 scanline = aBitmap.getAddr32(0, 0);
1542 *scanline++ = white;
1543 *scanline++ = black;
1544 scanline = aBitmap.getAddr32(0, 1);
1545 *scanline++ = black;
1546 *scanline++ = white;
1547 aBitmap.setImmutable();
1548 // The bitmap is repeated in both directions the checker pattern is as big
1549 // as the polygon (usually rectangle)
1550 aPaint.setShader(aBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
1551 SkSamplingOptions()));
1554 #ifdef SK_METAL
1555 // tdf#153306 prevent subpixel shifting of X coordinate
1556 // HACK: for some unknown reason, if the X coordinate of the
1557 // path's bounds is more than 1024, SkBlendMode::kExclusion will
1558 // shift by about a half a pixel to the right with Skia/Metal on
1559 // a Retina display. Weirdly, if the same polygon is repeatedly
1560 // drawn, the total shift is cumulative so if the drawn polygon
1561 // is more than a few pixels wide, the blinking cursor in Writer
1562 // will exhibit this bug but only for one thin vertical slice at
1563 // a time. Apparently, shifting drawing a very tiny amount to
1564 // the left seems to be enough to quell this runaway cumulative
1565 // X coordinate shift.
1566 if (isGPU())
1568 SkMatrix aMatrix;
1569 aMatrix.set(SkMatrix::kMTransX, -0.001);
1570 getDrawCanvas()->concat(aMatrix);
1572 #endif
1574 getDrawCanvas()->drawPath(aPath, aPaint);
1576 postDraw();
1579 void SkiaSalGraphicsImpl::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
1580 tools::Long nHeight, SalInvert eFlags)
1582 basegfx::B2DRectangle aRectangle(nX, nY, nX + nWidth, nY + nHeight);
1583 auto aRect = basegfx::utils::createPolygonFromRect(aRectangle);
1584 invert(aRect, eFlags);
1587 void SkiaSalGraphicsImpl::invert(sal_uInt32 nPoints, const Point* pPointArray, SalInvert eFlags)
1589 basegfx::B2DPolygon aPolygon;
1590 aPolygon.append(basegfx::B2DPoint(pPointArray[0].getX(), pPointArray[0].getY()), nPoints);
1591 for (sal_uInt32 i = 1; i < nPoints; ++i)
1593 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPointArray[i].getX(), pPointArray[i].getY()));
1595 aPolygon.setClosed(true);
1597 invert(aPolygon, eFlags);
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)
1607 if (alphaBitmap)
1608 assert(bitmap.GetSize() == alphaBitmap->GetSize());
1610 if (targetSize.IsEmpty())
1611 return {};
1612 if (alphaBitmap && alphaBitmap->IsFullyOpaqueAsAlpha())
1613 alphaBitmap = nullptr; // the alpha can be ignored
1614 if (bitmap.PreferSkShader() && (!alphaBitmap || alphaBitmap->PreferSkShader()))
1615 return {};
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)
1628 bitmapReady = true;
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);
1633 if (alphaBitmap)
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,
1655 DirectImage::Yes);
1656 if (sk_sp<SkImage> image = findCachedImage(key))
1658 assert(imageSize(image) == targetSize);
1659 return image;
1663 // Probably not much point in caching of just doing a copy.
1664 if (alphaBitmap == nullptr && targetSize == bitmap.GetSize())
1665 return {};
1666 // Image too small to be worth caching if not scaling.
1667 if (targetSize == bitmap.GetSize() && targetSize.Width() < 100 && targetSize.Height() < 100)
1668 return {};
1669 // GPU-accelerated drawing with SkShader should be fast enough to not need caching.
1670 if (isGPU())
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)
1677 return {};
1679 // Do not cache the result if it would take most of the cache and thus get evicted soon.
1680 if (targetSize.Width() * targetSize.Height() * 4 > maxImageCacheSize() * 0.7)
1681 return {};
1683 // Use ready direct image if they are both available, now even the size doesn't matter
1684 // (we'll scale as necessary and it's better to scale from the original). Require only
1685 // that they are the same size, or that one prefers a shader or doesn't exist
1686 // (i.e. avoid two images of different size).
1687 bitmapReady = bitmap.GetSkImage(DirectImage::Yes) != nullptr;
1688 alphaBitmapReady = alphaBitmap && alphaBitmap->GetAlphaSkImage(DirectImage::Yes) != nullptr;
1689 if (bitmapReady && alphaBitmap && !alphaBitmapReady && !alphaBitmap->PreferSkShader())
1690 bitmapReady = false;
1691 if (alphaBitmapReady && !bitmapReady && bitmap.PreferSkShader())
1692 alphaBitmapReady = false;
1694 DirectImage bitmapType = bitmapReady ? DirectImage::Yes : DirectImage::No;
1695 DirectImage alphaBitmapType = alphaBitmapReady ? DirectImage::Yes : DirectImage::No;
1697 // Try to find a cached result, this time after possible delayed scaling.
1698 OString key = makeCachedImageKey(bitmap, alphaBitmap, targetSize, bitmapType, alphaBitmapType);
1699 if (sk_sp<SkImage> image = findCachedImage(key))
1701 assert(imageSize(image) == targetSize);
1702 return image;
1705 // In some cases (tdf#134237) the target size may be very large. In that case it's
1706 // better to rely on Skia to clip and draw only the necessary, rather than prepare
1707 // a very large image only to not use most of it. Do this only after checking whether
1708 // the image is already cached, since it might have been already cached in a previous
1709 // call that had the draw area large enough to be seen as worth caching.
1710 const Size drawAreaSize = mClipRegion.GetBoundRect().GetSize() * mScaling;
1711 if (targetSize.Width() > drawAreaSize.Width() || targetSize.Height() > drawAreaSize.Height())
1713 // This is a bit tricky. The condition above just checks that at least a part of the resulting
1714 // image will not be used (it's larger then our drawing area). But this may often happen
1715 // when just scrolling a document with a large image, where the caching may very well be worth it.
1716 // Since the problem is mainly the cost of upscaling and then the size of the resulting bitmap,
1717 // compute a ratio of how much this is going to be scaled up, how much this is larger than
1718 // the drawing area, and then refuse to cache if it's too much.
1719 const double upscaleRatio
1720 = std::max(1.0, 1.0 * targetSize.Width() / bitmap.GetSize().Width()
1721 * targetSize.Height() / bitmap.GetSize().Height());
1722 const double oversizeRatio = 1.0 * targetSize.Width() / drawAreaSize.Width()
1723 * targetSize.Height() / drawAreaSize.Height();
1724 const double ratio = upscaleRatio * oversizeRatio;
1725 if (ratio > 4)
1727 SAL_INFO("vcl.skia.trace", "mergecachebitmaps("
1728 << this << "): not caching, ratio:" << ratio << ", "
1729 << bitmap.GetSize() << "->" << targetSize << " in "
1730 << drawAreaSize);
1731 return {};
1735 Size sourceSize;
1736 if (bitmapReady)
1737 sourceSize = imageSize(bitmap.GetSkImage(DirectImage::Yes));
1738 else if (alphaBitmapReady)
1739 sourceSize = imageSize(alphaBitmap->GetAlphaSkImage(DirectImage::Yes));
1740 else
1741 sourceSize = bitmap.GetSize();
1743 // Generate a new result and cache it.
1744 sk_sp<SkSurface> tmpSurface
1745 = createSkSurface(targetSize, alphaBitmap ? kPremul_SkAlphaType : bitmap.alphaType());
1746 if (!tmpSurface)
1747 return nullptr;
1748 SkCanvas* canvas = tmpSurface->getCanvas();
1750 SkAutoCanvasRestore autoRestore(canvas, true);
1751 SkPaint paint;
1752 SkSamplingOptions samplingOptions;
1753 if (targetSize != sourceSize)
1755 SkMatrix matrix;
1756 matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / sourceSize.Width());
1757 matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / sourceSize.Height());
1758 canvas->concat(matrix);
1759 if (!isUnitTestRunning()) // unittests want exact pixel values
1760 samplingOptions = makeSamplingOptions(matrix, 1);
1762 if (alphaBitmap != nullptr)
1764 canvas->clear(SK_ColorTRANSPARENT);
1765 paint.setShader(SkShaders::Blend(
1766 SkBlendMode::kDstIn, bitmap.GetSkShader(samplingOptions, bitmapType),
1767 alphaBitmap->GetAlphaSkShader(samplingOptions, alphaBitmapType)));
1768 canvas->drawPaint(paint);
1770 else if (bitmap.PreferSkShader())
1772 paint.setShader(bitmap.GetSkShader(samplingOptions, bitmapType));
1773 canvas->drawPaint(paint);
1775 else
1776 canvas->drawImage(bitmap.GetSkImage(bitmapType), 0, 0, samplingOptions, &paint);
1777 if (isGPU())
1778 SAL_INFO("vcl.skia.trace", "mergecachebitmaps(" << this << "): caching GPU downscaling:"
1779 << bitmap.GetSize() << "->"
1780 << targetSize);
1782 sk_sp<SkImage> image = makeCheckedImageSnapshot(tmpSurface);
1783 addCachedImage(key, image);
1784 return image;
1787 OString SkiaSalGraphicsImpl::makeCachedImageKey(const SkiaSalBitmap& bitmap,
1788 const SkiaSalBitmap* alphaBitmap,
1789 const Size& targetSize, DirectImage bitmapType,
1790 DirectImage alphaBitmapType)
1792 OString key = OString::number(targetSize.Width()) + "x" + OString::number(targetSize.Height())
1793 + "_" + bitmap.GetImageKey(bitmapType);
1794 if (alphaBitmap)
1795 key += "_" + alphaBitmap->GetAlphaImageKey(alphaBitmapType);
1796 return key;
1799 bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
1800 const SalBitmap& rAlphaBitmap)
1802 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1803 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1804 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1805 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1806 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1807 // alpha blending or scaling.
1808 SalTwoRect imagePosAry(rPosAry);
1809 Size imageSize = rSourceBitmap.GetSize();
1810 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1811 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1812 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1813 && rPosAry.mnSrcWidth == rSourceBitmap.GetSize().Width()
1814 && rPosAry.mnSrcHeight == rSourceBitmap.GetSize().Height())
1816 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1817 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1818 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1820 sk_sp<SkImage> image
1821 = mergeCacheBitmaps(rSkiaSourceBitmap, &rSkiaAlphaBitmap, imageSize * mScaling);
1822 if (image)
1823 drawImage(imagePosAry, image, mScaling);
1824 else if (rSkiaAlphaBitmap.IsFullyOpaqueAsAlpha()
1825 && !rSkiaSourceBitmap.PreferSkShader()) // alpha can be ignored
1826 drawBitmap(rPosAry, rSkiaSourceBitmap);
1827 else
1828 drawShader(rPosAry,
1829 SkShaders::Blend(
1830 SkBlendMode::kDstIn,
1831 rSkiaSourceBitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)),
1832 rSkiaAlphaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
1833 return true;
1836 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBitmap& bitmap,
1837 SkBlendMode blendMode)
1839 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1840 // scaling.
1841 SalTwoRect imagePosAry(rPosAry);
1842 Size imageSize = bitmap.GetSize();
1843 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1844 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1845 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1846 && rPosAry.mnSrcWidth == bitmap.GetSize().Width()
1847 && rPosAry.mnSrcHeight == bitmap.GetSize().Height())
1849 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1850 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1851 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1853 sk_sp<SkImage> image = mergeCacheBitmaps(bitmap, nullptr, imageSize * mScaling);
1854 if (image)
1855 drawImage(imagePosAry, image, mScaling, blendMode);
1856 else if (bitmap.PreferSkShader())
1857 drawShader(rPosAry, bitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)), blendMode);
1858 else
1859 drawImage(rPosAry, bitmap.GetSkImage(), 1, blendMode);
1862 void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage,
1863 int srcScaling, SkBlendMode eBlendMode)
1865 SkRect aSourceRect
1866 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1867 if (srcScaling != 1)
1868 aSourceRect = scaleRect(aSourceRect, srcScaling);
1869 SkRect aDestinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY,
1870 rPosAry.mnDestWidth, rPosAry.mnDestHeight);
1872 SkPaint aPaint = makeBitmapPaint();
1873 aPaint.setBlendMode(eBlendMode);
1875 preDraw();
1876 SAL_INFO("vcl.skia.trace",
1877 "drawimage(" << this << "): " << rPosAry << ":" << SkBlendMode_Name(eBlendMode));
1878 addUpdateRegion(aDestinationRect);
1879 getDrawCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect,
1880 makeSamplingOptions(rPosAry, mScaling, srcScaling), &aPaint,
1881 SkCanvas::kFast_SrcRectConstraint);
1882 ++pendingOperationsToFlush; // tdf#136369
1883 postDraw();
1886 // SkShader can be used to merge multiple bitmaps with appropriate blend modes (e.g. when
1887 // merging a bitmap with its alpha mask).
1888 void SkiaSalGraphicsImpl::drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader,
1889 SkBlendMode blendMode)
1891 preDraw();
1892 SAL_INFO("vcl.skia.trace", "drawshader(" << this << "): " << rPosAry);
1893 SkRect destinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1894 rPosAry.mnDestHeight);
1895 addUpdateRegion(destinationRect);
1896 SkPaint paint = makeBitmapPaint();
1897 paint.setBlendMode(blendMode);
1898 paint.setShader(shader);
1899 SkCanvas* canvas = getDrawCanvas();
1900 // Scaling needs to be done explicitly using a matrix.
1902 SkAutoCanvasRestore autoRestore(canvas, true);
1903 SkMatrix matrix = SkMatrix::Translate(rPosAry.mnDestX, rPosAry.mnDestY)
1904 * SkMatrix::Scale(1.0 * rPosAry.mnDestWidth / rPosAry.mnSrcWidth,
1905 1.0 * rPosAry.mnDestHeight / rPosAry.mnSrcHeight)
1906 * SkMatrix::Translate(-rPosAry.mnSrcX, -rPosAry.mnSrcY);
1907 #ifndef NDEBUG
1908 // Handle floating point imprecisions, round p1 to 2 decimal places.
1909 auto compareRounded = [](const SkPoint& p1, const SkPoint& p2) {
1910 return rtl::math::round(p1.x(), 2) == p2.x() && rtl::math::round(p1.y(), 2) == p2.y();
1912 #endif
1913 assert(compareRounded(matrix.mapXY(rPosAry.mnSrcX, rPosAry.mnSrcY),
1914 SkPoint::Make(rPosAry.mnDestX, rPosAry.mnDestY)));
1915 assert(compareRounded(
1916 matrix.mapXY(rPosAry.mnSrcX + rPosAry.mnSrcWidth, rPosAry.mnSrcY + rPosAry.mnSrcHeight),
1917 SkPoint::Make(rPosAry.mnDestX + rPosAry.mnDestWidth,
1918 rPosAry.mnDestY + rPosAry.mnDestHeight)));
1919 canvas->concat(matrix);
1920 SkRect sourceRect = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth,
1921 rPosAry.mnSrcHeight);
1922 canvas->drawRect(sourceRect, paint);
1924 postDraw();
1927 bool SkiaSalGraphicsImpl::hasFastDrawTransformedBitmap() const
1929 // Return true even in raster mode, even that way Skia is faster than e.g. GraphicObject
1930 // trying to handle stuff manually.
1931 return true;
1934 // Whether applying matrix needs image smoothing for the transformation.
1935 static bool matrixNeedsHighQuality(const SkMatrix& matrix)
1937 if (matrix.isIdentity())
1938 return false;
1939 if (matrix.isScaleTranslate())
1941 if (abs(matrix.getScaleX()) == 1 && abs(matrix.getScaleY()) == 1)
1942 return false; // Only at most flipping and keeping the size.
1943 return true;
1945 assert(!matrix.hasPerspective()); // we do not use this
1946 if (matrix.getScaleX() == 0 && matrix.getScaleY() == 0)
1948 // Rotating 90 or 270 degrees while keeping the size.
1949 if ((matrix.getSkewX() == 1 && matrix.getSkewY() == -1)
1950 || (matrix.getSkewX() == -1 && matrix.getSkewY() == 1))
1951 return false;
1953 return true;
1956 namespace SkiaTests
1958 bool matrixNeedsHighQuality(const SkMatrix& matrix) { return ::matrixNeedsHighQuality(matrix); }
1961 bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
1962 const basegfx::B2DPoint& rX,
1963 const basegfx::B2DPoint& rY,
1964 const SalBitmap& rSourceBitmap,
1965 const SalBitmap* pAlphaBitmap, double fAlpha)
1967 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1968 assert(!pAlphaBitmap || dynamic_cast<const SkiaSalBitmap*>(pAlphaBitmap));
1970 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1971 const SkiaSalBitmap* pSkiaAlphaBitmap = static_cast<const SkiaSalBitmap*>(pAlphaBitmap);
1973 if (pSkiaAlphaBitmap && pSkiaAlphaBitmap->IsFullyOpaqueAsAlpha())
1974 pSkiaAlphaBitmap = nullptr; // the alpha can be ignored
1976 // Setup the image transformation,
1977 // using the rNull, rX, rY points as destinations for the (0,0), (Width,0), (0,Height) source points.
1978 const basegfx::B2DVector aXRel = rX - rNull;
1979 const basegfx::B2DVector aYRel = rY - rNull;
1981 preDraw();
1982 SAL_INFO("vcl.skia.trace", "drawtransformedbitmap(" << this << "): " << rSourceBitmap.GetSize()
1983 << " " << rNull << ":" << rX << ":" << rY);
1985 addUpdateRegion(SkRect::MakeWH(GetWidth(), GetHeight())); // can't tell, use whole area
1986 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1987 // alpha blending or scaling.
1988 // The extra fAlpha blending is not cached, with the assumption that it usually gradually changes
1989 // for each invocation.
1990 // Pass size * mScaling to mergeCacheBitmaps() so that it prepares the size that will be needed
1991 // after the mScaling-scaling matrix, but otherwise calculate everything else using the VCL coordinates.
1992 Size imageSize(round(aXRel.getLength()), round(aYRel.getLength()));
1993 sk_sp<SkImage> imageToDraw
1994 = mergeCacheBitmaps(rSkiaBitmap, pSkiaAlphaBitmap, imageSize * mScaling);
1995 if (imageToDraw)
1997 SkMatrix matrix;
1998 // Round sizes for scaling, so that sub-pixel differences don't
1999 // trigger unnecessary scaling. Image has already been scaled
2000 // by mergeCacheBitmaps() and we shouldn't scale here again
2001 // unless the drawing is also skewed.
2002 matrix.set(SkMatrix::kMScaleX, round(aXRel.getX()) / imageSize.Width());
2003 matrix.set(SkMatrix::kMScaleY, round(aYRel.getY()) / imageSize.Height());
2004 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / imageSize.Width());
2005 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / imageSize.Height());
2006 matrix.set(SkMatrix::kMTransX, rNull.getX());
2007 matrix.set(SkMatrix::kMTransY, rNull.getY());
2008 SkCanvas* canvas = getDrawCanvas();
2009 SkAutoCanvasRestore autoRestore(canvas, true);
2010 canvas->concat(matrix);
2011 SkSamplingOptions samplingOptions;
2012 // If the matrix changes geometry, we need to smooth-scale. If there's mScaling,
2013 // that's already been handled by mergeCacheBitmaps().
2014 if (matrixNeedsHighQuality(matrix))
2015 samplingOptions = makeSamplingOptions(matrix, 1);
2016 if (fAlpha == 1.0)
2018 // Specify sizes to scale the image size back if needed (because of mScaling).
2019 SkRect dstRect = SkRect::MakeWH(imageSize.Width(), imageSize.Height());
2020 SkRect srcRect = SkRect::MakeWH(imageToDraw->width(), imageToDraw->height());
2021 SkPaint paint = makeBitmapPaint();
2022 canvas->drawImageRect(imageToDraw, srcRect, dstRect, samplingOptions, &paint,
2023 SkCanvas::kFast_SrcRectConstraint);
2025 else
2027 SkPaint paint = makeBitmapPaint();
2028 // Scale the image size back if needed.
2029 SkMatrix scale = SkMatrix::Scale(1.0 / mScaling, 1.0 / mScaling);
2030 paint.setShader(SkShaders::Blend(
2031 SkBlendMode::kDstIn, imageToDraw->makeShader(samplingOptions, &scale),
2032 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
2033 canvas->drawRect(SkRect::MakeWH(imageSize.Width(), imageSize.Height()), paint);
2036 else
2038 SkMatrix matrix;
2039 const Size aSize = rSourceBitmap.GetSize();
2040 matrix.set(SkMatrix::kMScaleX, aXRel.getX() / aSize.Width());
2041 matrix.set(SkMatrix::kMScaleY, aYRel.getY() / aSize.Height());
2042 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / aSize.Width());
2043 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / aSize.Height());
2044 matrix.set(SkMatrix::kMTransX, rNull.getX());
2045 matrix.set(SkMatrix::kMTransY, rNull.getY());
2046 SkCanvas* canvas = getDrawCanvas();
2047 SkAutoCanvasRestore autoRestore(canvas, true);
2048 canvas->concat(matrix);
2049 SkSamplingOptions samplingOptions;
2050 if (matrixNeedsHighQuality(matrix) || (mScaling != 1 && !isUnitTestRunning()))
2051 samplingOptions = makeSamplingOptions(matrix, mScaling);
2052 if (pSkiaAlphaBitmap)
2054 SkPaint paint = makeBitmapPaint();
2055 paint.setShader(SkShaders::Blend(SkBlendMode::kDstIn,
2056 rSkiaBitmap.GetSkShader(samplingOptions),
2057 pSkiaAlphaBitmap->GetAlphaSkShader(samplingOptions)));
2058 if (fAlpha != 1.0)
2059 paint.setShader(
2060 SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
2061 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
2062 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
2064 else if (rSkiaBitmap.PreferSkShader() || fAlpha != 1.0)
2066 SkPaint paint = makeBitmapPaint();
2067 paint.setShader(rSkiaBitmap.GetSkShader(samplingOptions));
2068 if (fAlpha != 1.0)
2069 paint.setShader(
2070 SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
2071 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
2072 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
2074 else
2076 SkPaint paint = makeBitmapPaint();
2077 canvas->drawImage(rSkiaBitmap.GetSkImage(), 0, 0, samplingOptions, &paint);
2080 postDraw();
2081 return true;
2084 bool SkiaSalGraphicsImpl::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
2085 tools::Long nHeight, sal_uInt8 nTransparency)
2087 privateDrawAlphaRect(nX, nY, nWidth, nHeight, nTransparency / 100.0);
2088 return true;
2091 bool SkiaSalGraphicsImpl::drawGradient(const tools::PolyPolygon& rPolyPolygon,
2092 const Gradient& rGradient)
2094 if (rGradient.GetStyle() != css::awt::GradientStyle_LINEAR
2095 && rGradient.GetStyle() != css::awt::GradientStyle_AXIAL
2096 && rGradient.GetStyle() != css::awt::GradientStyle_RADIAL)
2097 return false; // unsupported
2098 if (rGradient.GetSteps() != 0)
2099 return false; // We can't tell Skia how many colors to use in the gradient.
2100 preDraw();
2101 SAL_INFO("vcl.skia.trace", "drawgradient(" << this << "): " << rPolyPolygon.getB2DPolyPolygon()
2102 << ":" << static_cast<int>(rGradient.GetStyle()));
2103 tools::Rectangle boundRect(rPolyPolygon.GetBoundRect());
2104 if (boundRect.IsEmpty())
2105 return true;
2106 SkPath path;
2107 if (rPolyPolygon.IsRect())
2109 // Rect->Polygon conversion loses the right and bottom edge, fix that.
2110 path.addRect(SkRect::MakeXYWH(boundRect.getX(), boundRect.getY(), boundRect.GetWidth(),
2111 boundRect.GetHeight()));
2112 boundRect.AdjustRight(1);
2113 boundRect.AdjustBottom(1);
2115 else
2116 addPolyPolygonToPath(rPolyPolygon.getB2DPolyPolygon(), path);
2117 path.setFillType(SkPathFillType::kEvenOdd);
2118 addUpdateRegion(path.getBounds());
2120 Gradient aGradient(rGradient);
2121 tools::Rectangle aBoundRect;
2122 Point aCenter;
2123 aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10);
2124 aGradient.GetBoundRect(boundRect, aBoundRect, aCenter);
2126 SkColor startColor
2127 = toSkColorWithIntensity(rGradient.GetStartColor(), rGradient.GetStartIntensity());
2128 SkColor endColor = toSkColorWithIntensity(rGradient.GetEndColor(), rGradient.GetEndIntensity());
2130 sk_sp<SkShader> shader;
2131 if (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR)
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[2] = { startColor, endColor };
2138 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
2139 shader = SkGradientShader::MakeLinear(points, colors, pos, 2, SkTileMode::kClamp);
2141 else if (rGradient.GetStyle() == css::awt::GradientStyle_AXIAL)
2143 tools::Polygon aPoly(aBoundRect);
2144 aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
2145 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
2146 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
2147 SkColor colors[3] = { endColor, startColor, endColor };
2148 SkScalar border = SkDoubleToScalar(aGradient.GetBorder() / 100.0);
2149 SkScalar pos[3] = { std::min<SkScalar>(border * 0.5f, 0.5f), 0.5f,
2150 std::max<SkScalar>(1 - border * 0.5f, 0.5f) };
2151 shader = SkGradientShader::MakeLinear(points, colors, pos, 3, SkTileMode::kClamp);
2153 else
2155 // Move the center by (-1,-1) (the default VCL algorithm is a bit off-center that way,
2156 // Skia is the opposite way).
2157 SkPoint center = SkPoint::Make(toSkX(aCenter.X()) - 1, toSkY(aCenter.Y()) - 1);
2158 SkScalar radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
2159 SkColor colors[2] = { endColor, startColor };
2160 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
2161 shader = SkGradientShader::MakeRadial(center, radius, colors, pos, 2, SkTileMode::kClamp);
2164 SkPaint paint = makeGradientPaint();
2165 paint.setAntiAlias(mParent.getAntiAlias());
2166 paint.setShader(shader);
2167 getDrawCanvas()->drawPath(path, paint);
2168 postDraw();
2169 return true;
2172 bool SkiaSalGraphicsImpl::implDrawGradient(const basegfx::B2DPolyPolygon& rPolyPolygon,
2173 const SalGradient& rGradient)
2175 preDraw();
2176 SAL_INFO("vcl.skia.trace",
2177 "impldrawgradient(" << this << "): " << rPolyPolygon << ":" << rGradient.maPoint1
2178 << "->" << rGradient.maPoint2 << ":" << rGradient.maStops.size());
2180 SkPath path;
2181 addPolyPolygonToPath(rPolyPolygon, path);
2182 path.setFillType(SkPathFillType::kEvenOdd);
2183 addUpdateRegion(path.getBounds());
2185 SkPoint points[2]
2186 = { SkPoint::Make(toSkX(rGradient.maPoint1.getX()), toSkY(rGradient.maPoint1.getY())),
2187 SkPoint::Make(toSkX(rGradient.maPoint2.getX()), toSkY(rGradient.maPoint2.getY())) };
2188 std::vector<SkColor> colors;
2189 std::vector<SkScalar> pos;
2190 for (const SalGradientStop& stop : rGradient.maStops)
2192 colors.emplace_back(toSkColor(stop.maColor));
2193 pos.emplace_back(stop.mfOffset);
2195 sk_sp<SkShader> shader = SkGradientShader::MakeLinear(points, colors.data(), pos.data(),
2196 colors.size(), SkTileMode::kDecal);
2197 SkPaint paint = makeGradientPaint();
2198 paint.setAntiAlias(mParent.getAntiAlias());
2199 paint.setShader(shader);
2200 getDrawCanvas()->drawPath(path, paint);
2201 postDraw();
2202 return true;
2205 static double toRadian(Degree10 degree10th) { return toRadians(3600_deg10 - degree10th); }
2206 static auto toCos(Degree10 degree10th) { return SkScalarCos(toRadian(degree10th)); }
2207 static auto toSin(Degree10 degree10th) { return SkScalarSin(toRadian(degree10th)); }
2209 void SkiaSalGraphicsImpl::drawGenericLayout(const GenericSalLayout& layout, Color textColor,
2210 const SkFont& font, const SkFont& verticalFont)
2212 SkiaZone zone;
2213 std::vector<SkGlyphID> glyphIds;
2214 std::vector<SkRSXform> glyphForms;
2215 std::vector<bool> verticals;
2216 glyphIds.reserve(256);
2217 glyphForms.reserve(256);
2218 verticals.reserve(256);
2219 basegfx::B2DPoint aPos;
2220 const GlyphItem* pGlyph;
2221 int nStart = 0;
2222 auto cos = toCos(layout.GetOrientation());
2223 auto sin = toSin(layout.GetOrientation());
2224 while (layout.GetNextGlyph(&pGlyph, aPos, nStart))
2226 glyphIds.push_back(pGlyph->glyphId());
2227 verticals.emplace_back(pGlyph->IsVertical());
2228 auto cos1 = pGlyph->IsVertical() ? sin : cos; // cos (x - 90) = sin (x)
2229 auto sin1 = pGlyph->IsVertical() ? -cos : sin; // sin (x - 90) = -cos (x)
2230 SkRSXform form = SkRSXform::Make(cos1, sin1, aPos.getX(), aPos.getY());
2231 glyphForms.emplace_back(std::move(form));
2233 if (glyphIds.empty())
2234 return;
2236 preDraw();
2237 auto getBoundRect = [&layout]() {
2238 basegfx::B2DRectangle rect;
2239 layout.GetBoundRect(rect);
2240 return rect;
2242 SAL_INFO("vcl.skia.trace", "drawtextblob(" << this << "): " << getBoundRect() << ", "
2243 << glyphIds.size() << " glyphs, " << textColor);
2245 // Vertical glyphs need a different font, so split drawing into runs that each
2246 // draw only consecutive horizontal or vertical glyphs.
2247 std::vector<bool>::const_iterator pos = verticals.cbegin();
2248 std::vector<bool>::const_iterator end = verticals.cend();
2249 while (pos != end)
2251 bool verticalRun = *pos;
2252 std::vector<bool>::const_iterator rangeEnd = std::find(pos + 1, end, !verticalRun);
2253 size_t index = pos - verticals.cbegin();
2254 size_t count = rangeEnd - pos;
2255 sk_sp<SkTextBlob> textBlob = SkTextBlob::MakeFromRSXform(
2256 glyphIds.data() + index, count * sizeof(SkGlyphID), glyphForms.data() + index,
2257 verticalRun ? verticalFont : font, SkTextEncoding::kGlyphID);
2258 addUpdateRegion(textBlob->bounds());
2259 SkPaint paint = makeTextPaint(textColor);
2260 getDrawCanvas()->drawTextBlob(textBlob, 0, 0, paint);
2261 pos = rangeEnd;
2263 postDraw();
2266 bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType /*eType*/) const { return false; }
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"))
2273 return atoi(env);
2274 return 1;
2277 int SkiaSalGraphicsImpl::getWindowScaling() const
2279 static const int scaling = getScaling();
2280 return scaling;
2283 void SkiaSalGraphicsImpl::dump(const char* file) const
2285 assert(mSurface.get());
2286 SkiaHelper::dump(mSurface, file);
2289 void SkiaSalGraphicsImpl::windowBackingPropertiesChanged()
2291 if (mInWindowBackingPropertiesChanged || !isGPU())
2292 return;
2294 mInWindowBackingPropertiesChanged = true;
2295 performFlush();
2296 mInWindowBackingPropertiesChanged = false;
2299 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */