bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / skia / gdiimpl.cxx
blob2ae3b8d85e23a215b3a2c2472fcb05fcd1b65fb7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <skia/gdiimpl.hxx>
22 #include <salgdi.hxx>
23 #include <skia/salbmp.hxx>
24 #include <vcl/idle.hxx>
25 #include <vcl/svapp.hxx>
26 #include <vcl/lazydelete.hxx>
27 #include <vcl/gradient.hxx>
28 #include <vcl/skia/SkiaHelper.hxx>
29 #include <skia/utils.hxx>
30 #include <skia/zone.hxx>
32 #include <SkBitmap.h>
33 #include <SkCanvas.h>
34 #include <SkGradientShader.h>
35 #include <SkPath.h>
36 #include <SkRegion.h>
37 #include <SkPathEffect.h>
38 #include <SkDashPathEffect.h>
39 #include <GrBackendSurface.h>
40 #include <SkTextBlob.h>
41 #include <SkRSXform.h>
43 #include <numeric>
44 #include <sstream>
46 #include <basegfx/polygon/b2dpolygontools.hxx>
47 #include <basegfx/polygon/b2dpolypolygontools.hxx>
48 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
49 #include <o3tl/sorted_vector.hxx>
50 #include <rtl/math.hxx>
52 using namespace SkiaHelper;
54 namespace
56 // Create Skia Path from B2DPolygon
57 // Note that polygons generally have the complication that when used
58 // for area (fill) operations they usually miss the right-most and
59 // bottom-most line of pixels of the bounding rectangle (see
60 // https://lists.freedesktop.org/archives/libreoffice/2019-November/083709.html).
61 // So be careful with rectangle->polygon conversions (generally avoid them).
62 void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath, sal_uInt32 nFirstIndex,
63 sal_uInt32 nLastIndex, const sal_uInt32 nPointCount, const bool bClosePath,
64 const bool bHasCurves, bool* hasOnlyOrthogonal = nullptr)
66 assert(nFirstIndex < nPointCount);
67 assert(nLastIndex <= nPointCount);
69 if (nPointCount <= 1)
70 return;
72 bool bFirst = true;
73 sal_uInt32 nPreviousIndex = nFirstIndex == 0 ? nPointCount - 1 : nFirstIndex - 1;
74 basegfx::B2DPoint aPreviousPoint = rPolygon.getB2DPoint(nPreviousIndex);
76 for (sal_uInt32 nIndex = nFirstIndex; nIndex <= nLastIndex; nIndex++)
78 if (nIndex == nPointCount && !bClosePath)
79 continue;
81 // Make sure we loop the last point to first point
82 sal_uInt32 nCurrentIndex = nIndex % nPointCount;
83 basegfx::B2DPoint aCurrentPoint = rPolygon.getB2DPoint(nCurrentIndex);
85 if (bFirst)
87 rPath.moveTo(aCurrentPoint.getX(), aCurrentPoint.getY());
88 bFirst = false;
90 else if (!bHasCurves)
92 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY());
93 // If asked for, check whether the polygon has a line that is not
94 // strictly horizontal or vertical.
95 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
96 && aCurrentPoint.getY() != aPreviousPoint.getY())
97 *hasOnlyOrthogonal = false;
99 else
101 basegfx::B2DPoint aPreviousControlPoint = rPolygon.getNextControlPoint(nPreviousIndex);
102 basegfx::B2DPoint aCurrentControlPoint = rPolygon.getPrevControlPoint(nCurrentIndex);
104 if (aPreviousControlPoint.equal(aPreviousPoint)
105 && aCurrentControlPoint.equal(aCurrentPoint))
107 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY()); // a straight line
108 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
109 && aCurrentPoint.getY() != aPreviousPoint.getY())
110 *hasOnlyOrthogonal = false;
112 else
114 if (aPreviousControlPoint.equal(aPreviousPoint))
116 aPreviousControlPoint
117 = aPreviousPoint + ((aPreviousControlPoint - aCurrentPoint) * 0.0005);
119 if (aCurrentControlPoint.equal(aCurrentPoint))
121 aCurrentControlPoint
122 = aCurrentPoint + ((aCurrentControlPoint - aPreviousPoint) * 0.0005);
124 rPath.cubicTo(aPreviousControlPoint.getX(), aPreviousControlPoint.getY(),
125 aCurrentControlPoint.getX(), aCurrentControlPoint.getY(),
126 aCurrentPoint.getX(), aCurrentPoint.getY());
127 if (hasOnlyOrthogonal != nullptr)
128 *hasOnlyOrthogonal = false;
131 aPreviousPoint = aCurrentPoint;
132 nPreviousIndex = nCurrentIndex;
134 if (bClosePath && nFirstIndex == 0 && nLastIndex == nPointCount)
136 rPath.close();
140 void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath,
141 bool* hasOnlyOrthogonal = nullptr)
143 addPolygonToPath(rPolygon, rPath, 0, rPolygon.count(), rPolygon.count(), rPolygon.isClosed(),
144 rPolygon.areControlPointsUsed(), hasOnlyOrthogonal);
147 void addPolyPolygonToPath(const basegfx::B2DPolyPolygon& rPolyPolygon, SkPath& rPath,
148 bool* hasOnlyOrthogonal = nullptr)
150 const sal_uInt32 nPolygonCount(rPolyPolygon.count());
152 if (nPolygonCount == 0)
153 return;
155 sal_uInt32 nPointCount = 0;
156 for (const auto& rPolygon : rPolyPolygon)
157 nPointCount += rPolygon.count() * 3; // because cubicTo is 3 elements
158 rPath.incReserve(nPointCount);
160 for (const auto& rPolygon : rPolyPolygon)
162 addPolygonToPath(rPolygon, rPath, hasOnlyOrthogonal);
166 // Check if the given polygon contains a straight line. If not, it consists
167 // solely of curves.
168 bool polygonContainsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
170 if (!rPolyPolygon.areControlPointsUsed())
171 return true; // no curves at all
172 for (const auto& rPolygon : rPolyPolygon)
174 const sal_uInt32 nPointCount(rPolygon.count());
175 bool bFirst = true;
177 const bool bClosePath(rPolygon.isClosed());
179 sal_uInt32 nCurrentIndex = 0;
180 sal_uInt32 nPreviousIndex = nPointCount - 1;
182 basegfx::B2DPoint aCurrentPoint;
183 basegfx::B2DPoint aPreviousPoint;
185 for (sal_uInt32 nIndex = 0; nIndex <= nPointCount; nIndex++)
187 if (nIndex == nPointCount && !bClosePath)
188 continue;
190 // Make sure we loop the last point to first point
191 nCurrentIndex = nIndex % nPointCount;
192 if (bFirst)
193 bFirst = false;
194 else
196 basegfx::B2DPoint aPreviousControlPoint
197 = rPolygon.getNextControlPoint(nPreviousIndex);
198 basegfx::B2DPoint aCurrentControlPoint
199 = rPolygon.getPrevControlPoint(nCurrentIndex);
201 if (aPreviousControlPoint.equal(aPreviousPoint)
202 && aCurrentControlPoint.equal(aCurrentPoint))
204 return true; // found a straight line
207 aPreviousPoint = aCurrentPoint;
208 nPreviousIndex = nCurrentIndex;
211 return false; // no straight line found
214 // returns true if the source or destination rectangles are invalid
215 bool checkInvalidSourceOrDestination(SalTwoRect const& rPosAry)
217 return rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
218 || rPosAry.mnDestHeight <= 0;
221 std::string dumpOptionalColor(const std::optional<Color>& c)
223 std::ostringstream oss;
224 if (c)
225 oss << *c;
226 else
227 oss << "no color";
229 return std::move(oss).str(); // optimized in C++20
232 } // end anonymous namespace
234 // Class that triggers flushing the backing buffer when idle.
235 class SkiaFlushIdle : public Idle
237 SkiaSalGraphicsImpl* mpGraphics;
238 #ifndef NDEBUG
239 char* debugname;
240 #endif
242 public:
243 explicit SkiaFlushIdle(SkiaSalGraphicsImpl* pGraphics)
244 : Idle(get_debug_name(pGraphics))
245 , mpGraphics(pGraphics)
247 // We don't want to be swapping before we've painted.
248 SetPriority(TaskPriority::POST_PAINT);
250 #ifndef NDEBUG
251 virtual ~SkiaFlushIdle() { free(debugname); }
252 #endif
253 const char* get_debug_name(SkiaSalGraphicsImpl* pGraphics)
255 #ifndef NDEBUG
256 // Idle keeps just a pointer, so we need to store the string
257 debugname = strdup(
258 OString("skia idle 0x" + OString::number(reinterpret_cast<sal_uIntPtr>(pGraphics), 16))
259 .getStr());
260 return debugname;
261 #else
262 (void)pGraphics;
263 return "skia idle";
264 #endif
267 virtual void Invoke() override
269 mpGraphics->performFlush();
270 Stop();
271 // tdf#157312 Don't change priority
272 // Instances of this class are constructed with
273 // TaskPriority::POST_PAINT, but then it was set to
274 // TaskPriority::HIGHEST when reused. Flushing
275 // seems to be expensive (at least with Skia/Metal) so keep the
276 // existing priority when reused.
277 SetPriority(TaskPriority::POST_PAINT);
281 SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvider* pProvider)
282 : mParent(rParent)
283 , mProvider(pProvider)
284 , mIsGPU(false)
285 , moLineColor(std::nullopt)
286 , moFillColor(std::nullopt)
287 , mXorMode(XorMode::None)
288 , mFlush(new SkiaFlushIdle(this))
289 , mScaling(1)
290 , mInWindowBackingPropertiesChanged(false)
294 SkiaSalGraphicsImpl::~SkiaSalGraphicsImpl()
296 assert(!mSurface);
297 assert(!mWindowContext);
300 void SkiaSalGraphicsImpl::Init() {}
302 void SkiaSalGraphicsImpl::createSurface()
304 SkiaZone zone;
305 if (isOffscreen())
306 createOffscreenSurface();
307 else
308 createWindowSurface();
309 mClipRegion = vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight()));
310 mDirtyRect = SkIRect::MakeWH(GetWidth(), GetHeight());
311 setCanvasScalingAndClipping();
313 // We don't want to be swapping before we've painted.
314 mFlush->Stop();
315 mFlush->SetPriority(TaskPriority::POST_PAINT);
318 void SkiaSalGraphicsImpl::createWindowSurface(bool forceRaster)
320 SkiaZone zone;
321 assert(!isOffscreen());
322 assert(!mSurface);
323 createWindowSurfaceInternal(forceRaster);
324 if (!mSurface)
326 switch (forceRaster ? RenderRaster : renderMethodToUse())
328 case RenderVulkan:
329 SAL_WARN("vcl.skia",
330 "cannot create Vulkan GPU window surface, falling back to Raster");
331 destroySurface(); // destroys also WindowContext
332 return createWindowSurface(true); // try again
333 case RenderMetal:
334 SAL_WARN("vcl.skia",
335 "cannot create Metal GPU window surface, falling back to Raster");
336 destroySurface(); // destroys also WindowContext
337 return createWindowSurface(true); // try again
338 case RenderRaster:
339 abort(); // This should not really happen, do not even try to cope with it.
342 mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
343 #ifdef DBG_UTIL
344 prefillSurface(mSurface);
345 #endif
348 bool SkiaSalGraphicsImpl::isOffscreen() const
350 if (mProvider == nullptr || mProvider->IsOffScreen())
351 return true;
352 // HACK: Sometimes (tdf#131939, tdf#138022, tdf#140288) VCL passes us a zero-sized window,
353 // and zero size is invalid for Skia, so force offscreen surface, where we handle this.
354 if (GetWidth() <= 0 || GetHeight() <= 0)
355 return true;
356 return false;
359 void SkiaSalGraphicsImpl::createOffscreenSurface()
361 SkiaZone zone;
362 assert(isOffscreen());
363 assert(!mSurface);
364 // HACK: See isOffscreen().
365 int width = std::max(1, GetWidth());
366 int height = std::max(1, GetHeight());
367 // We need to use window scaling even for offscreen surfaces, because the common usage is rendering something
368 // into an offscreen surface and then copy it to a window, so without scaling here the result would be originally
369 // drawn without scaling and only upscaled when drawing to a window.
370 mScaling = getWindowScaling();
371 mSurface = createSkSurface(width * mScaling, height * mScaling);
372 assert(mSurface);
373 mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
376 void SkiaSalGraphicsImpl::destroySurface()
378 SkiaZone zone;
379 if (mSurface)
381 // check setClipRegion() invariant
382 assert(mSurface->getCanvas()->getSaveCount() == 3);
383 // if this fails, something forgot to use SkAutoCanvasRestore
384 assert(mSurface->getCanvas()->getTotalMatrix() == SkMatrix::Scale(mScaling, mScaling));
386 mSurface.reset();
387 mWindowContext.reset();
388 mIsGPU = false;
389 mScaling = 1;
392 void SkiaSalGraphicsImpl::performFlush()
394 SkiaZone zone;
395 flushDrawing();
396 // Related: tdf#152703 Eliminate flickering during live resizing of a window
397 // When in live resize, the SkiaSalGraphicsImpl class does not detect that
398 // the window size has changed until after the flush has been called so
399 // call checkSurface() to recreate the SkSurface if needed before flushing.
400 checkSurface();
401 if (mSurface)
403 if (mDirtyRect.intersect(SkIRect::MakeWH(GetWidth(), GetHeight())))
404 flushSurfaceToWindowContext();
405 mDirtyRect.setEmpty();
409 void SkiaSalGraphicsImpl::flushSurfaceToWindowContext()
411 sk_sp<SkSurface> screenSurface = mWindowContext->getBackbufferSurface();
412 if (screenSurface != mSurface)
414 // GPU-based window contexts require calling getBackbufferSurface()
415 // for every swapBuffers(), for this reason mSurface is an offscreen surface
416 // where we keep the contents (LO does not do full redraws).
417 // So here blit the surface to the window context surface and then swap it.
418 assert(isGPU()); // Raster should always draw directly to backbuffer to save copying
419 SkPaint paint;
420 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
421 // We ignore mDirtyRect here, and mSurface already is in screenSurface coordinates,
422 // so no transformation needed.
423 screenSurface->getCanvas()->drawImage(makeCheckedImageSnapshot(mSurface), 0, 0,
424 SkSamplingOptions(), &paint);
425 screenSurface->flushAndSubmit(); // Otherwise the window is not drawn sometimes.
426 mWindowContext->swapBuffers(nullptr); // Must swap the entire surface.
428 else
430 // For raster mode use directly the backbuffer surface, it's just a bitmap
431 // surface anyway, and for those there's no real requirement to call
432 // getBackbufferSurface() repeatedly. Using our own surface would duplicate
433 // memory and cost time copying pixels around.
434 assert(!isGPU());
435 SkIRect dirtyRect = mDirtyRect;
436 if (mScaling != 1) // Adjust to mSurface coordinates if needed.
437 dirtyRect = scaleRect(dirtyRect, mScaling);
438 mWindowContext->swapBuffers(&dirtyRect);
442 void SkiaSalGraphicsImpl::DeInit() { destroySurface(); }
444 void SkiaSalGraphicsImpl::preDraw()
446 assert(comphelper::SolarMutex::get()->IsCurrentThread());
447 SkiaZone::enter(); // matched in postDraw()
448 checkSurface();
449 checkPendingDrawing();
452 void SkiaSalGraphicsImpl::postDraw()
454 scheduleFlush();
455 // Skia (at least when using Vulkan) queues drawing commands and executes them only later.
456 // But tdf#136369 leads to creating and queueing many tiny bitmaps, which makes
457 // Skia slow, and may make it even run out of memory. So force a flush if such
458 // a problematic operation has been performed too many times without a flush.
459 // Note that the counter is a static variable, as all drawing shares the same Skia drawing
460 // context (and so the flush here will also flush all drawing).
461 if (pendingOperationsToFlush > 1000)
463 mSurface->flushAndSubmit();
464 pendingOperationsToFlush = 0;
466 SkiaZone::leave(); // matched in preDraw()
467 // If there's a problem with the GPU context, abort.
468 if (GrDirectContext* context = GrAsDirectContext(mSurface->getCanvas()->recordingContext()))
470 // Running out of memory on the GPU technically could be possibly recoverable,
471 // but we don't know the exact status of the surface (and what has or has not been drawn to it),
472 // so in practice this is unrecoverable without possible data loss.
473 if (context->oomed())
475 SAL_WARN("vcl.skia", "GPU context has run out of memory, aborting.");
476 abort();
478 // Unrecoverable problem.
479 if (context->abandoned())
481 SAL_WARN("vcl.skia", "GPU context has been abandoned, aborting.");
482 abort();
487 void SkiaSalGraphicsImpl::scheduleFlush()
489 if (!isOffscreen())
491 if (!Application::IsInExecute())
492 performFlush(); // otherwise nothing would trigger idle rendering
493 else if (!mFlush->IsActive())
494 mFlush->Start();
498 // VCL can sometimes resize us without telling us, update the surface if needed.
499 // Also create the surface on demand if it has not been created yet (it is a waste
500 // to create it in Init() if it gets recreated later anyway).
501 void SkiaSalGraphicsImpl::checkSurface()
503 if (!mSurface)
505 createSurface();
506 SAL_INFO("vcl.skia.trace",
507 "create(" << this << "): " << Size(mSurface->width(), mSurface->height()));
509 else if (mInWindowBackingPropertiesChanged || GetWidth() * mScaling != mSurface->width()
510 || GetHeight() * mScaling != mSurface->height())
512 if (!avoidRecreateByResize())
514 Size oldSize(mSurface->width(), mSurface->height());
515 // Recreating a surface means that the old SkSurface contents will be lost.
516 // But if a window has been resized the windowing system may send repaint events
517 // only for changed parts and VCL would not repaint the whole area, assuming
518 // that some parts have not changed (this is what seems to cause tdf#131952).
519 // So carry over the old contents for windows, even though generally everything
520 // will be usually repainted anyway.
521 sk_sp<SkImage> snapshot;
522 if (!isOffscreen())
524 flushDrawing();
525 snapshot = makeCheckedImageSnapshot(mSurface);
528 destroySurface();
529 createSurface();
531 if (snapshot)
533 SkPaint paint;
534 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
535 // Scaling by current mScaling is active, undo that. We assume that the scaling
536 // does not change.
537 resetCanvasScalingAndClipping();
538 mSurface->getCanvas()->drawImage(snapshot, 0, 0, SkSamplingOptions(), &paint);
539 setCanvasScalingAndClipping();
541 SAL_INFO("vcl.skia.trace", "recreate(" << this << "): old " << oldSize << " new "
542 << Size(mSurface->width(), mSurface->height())
543 << " requested "
544 << Size(GetWidth(), GetHeight()));
549 bool SkiaSalGraphicsImpl::avoidRecreateByResize() const
551 // Keep the old surface if VCL sends us a broken size (see isOffscreen()).
552 if (GetWidth() == 0 || GetHeight() == 0)
553 return true;
554 return false;
557 void SkiaSalGraphicsImpl::flushDrawing()
559 if (!mSurface)
560 return;
561 checkPendingDrawing();
562 ++pendingOperationsToFlush;
565 void SkiaSalGraphicsImpl::setCanvasScalingAndClipping()
567 SkCanvas* canvas = mSurface->getCanvas();
568 assert(canvas->getSaveCount() == 1);
569 // If HiDPI scaling is active, simply set a scaling matrix for the canvas. This means
570 // that all painting can use VCL coordinates and they'll be automatically translated to mSurface
571 // scaled coordinates. If that is not wanted, the scale() state needs to be temporarily unset.
572 // State such as mDirtyRect is not scaled, the scaling matrix applies to clipping too,
573 // and the rest needs to be handled explicitly.
574 // When reading mSurface contents there's no automatic scaling and it needs to be handled explicitly.
575 canvas->save(); // keep the original state without any scaling
576 canvas->scale(mScaling, mScaling);
578 // SkCanvas::clipRegion() can only further reduce the clip region,
579 // but we need to set the given region, which may extend it.
580 // So handle that by always having the full clip region saved on the stack
581 // and always go back to that. SkCanvas::restore() only affects the clip
582 // and the matrix.
583 canvas->save(); // keep scaled state without clipping
584 setCanvasClipRegion(canvas, mClipRegion);
587 void SkiaSalGraphicsImpl::resetCanvasScalingAndClipping()
589 SkCanvas* canvas = mSurface->getCanvas();
590 assert(canvas->getSaveCount() == 3);
591 canvas->restore(); // undo clipping
592 canvas->restore(); // undo scaling
595 void SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
597 if (mClipRegion == region)
598 return;
599 SkiaZone zone;
600 checkPendingDrawing();
601 checkSurface();
602 mClipRegion = region;
603 SAL_INFO("vcl.skia.trace", "setclipregion(" << this << "): " << region);
604 SkCanvas* canvas = mSurface->getCanvas();
605 assert(canvas->getSaveCount() == 3);
606 canvas->restore(); // undo previous clip state, see setCanvasScalingAndClipping()
607 canvas->save();
608 setCanvasClipRegion(canvas, region);
611 void SkiaSalGraphicsImpl::setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region)
613 SkiaZone zone;
614 SkPath path;
615 // Always use region rectangles, regardless of what the region uses internally.
616 // That's what other VCL backends do, and trying to use addPolyPolygonToPath()
617 // in case a polygon is used leads to off-by-one errors such as tdf#133208.
618 RectangleVector rectangles;
619 region.GetRegionRectangles(rectangles);
620 path.incReserve(rectangles.size() + 1);
621 for (const tools::Rectangle& rectangle : rectangles)
622 path.addRect(SkRect::MakeXYWH(rectangle.getX(), rectangle.getY(), rectangle.GetWidth(),
623 rectangle.GetHeight()));
624 path.setFillType(SkPathFillType::kEvenOdd);
625 canvas->clipPath(path);
628 void SkiaSalGraphicsImpl::ResetClipRegion()
630 setClipRegion(vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight())));
633 const vcl::Region& SkiaSalGraphicsImpl::getClipRegion() const { return mClipRegion; }
635 sal_uInt16 SkiaSalGraphicsImpl::GetBitCount() const { return 32; }
637 tools::Long SkiaSalGraphicsImpl::GetGraphicsWidth() const { return GetWidth(); }
639 void SkiaSalGraphicsImpl::SetLineColor()
641 checkPendingDrawing();
642 moLineColor = std::nullopt;
645 void SkiaSalGraphicsImpl::SetLineColor(Color nColor)
647 checkPendingDrawing();
648 moLineColor = nColor;
651 void SkiaSalGraphicsImpl::SetFillColor()
653 checkPendingDrawing();
654 moFillColor = std::nullopt;
657 void SkiaSalGraphicsImpl::SetFillColor(Color nColor)
659 checkPendingDrawing();
660 moFillColor = nColor;
663 void SkiaSalGraphicsImpl::SetXORMode(bool set, bool invert)
665 XorMode newMode = set ? (invert ? XorMode::Invert : XorMode::Xor) : XorMode::None;
666 if (newMode == mXorMode)
667 return;
668 checkPendingDrawing();
669 SAL_INFO("vcl.skia.trace", "setxormode(" << this << "): " << set << "/" << invert);
670 mXorMode = newMode;
673 void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor)
675 checkPendingDrawing();
676 switch (nROPColor)
678 case SalROPColor::N0:
679 moLineColor = Color(0, 0, 0);
680 break;
681 case SalROPColor::N1:
682 moLineColor = Color(0xff, 0xff, 0xff);
683 break;
684 case SalROPColor::Invert:
685 moLineColor = Color(0xff, 0xff, 0xff);
686 break;
690 void SkiaSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor)
692 checkPendingDrawing();
693 switch (nROPColor)
695 case SalROPColor::N0:
696 moFillColor = Color(0, 0, 0);
697 break;
698 case SalROPColor::N1:
699 moFillColor = Color(0xff, 0xff, 0xff);
700 break;
701 case SalROPColor::Invert:
702 moFillColor = Color(0xff, 0xff, 0xff);
703 break;
707 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY)
709 drawPixel(nX, nY, *moLineColor);
712 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
714 preDraw();
715 SAL_INFO("vcl.skia.trace", "drawpixel(" << this << "): " << Point(nX, nY) << ":" << nColor);
716 addUpdateRegion(SkRect::MakeXYWH(nX, nY, 1, 1));
717 SkPaint paint = makePixelPaint(nColor);
718 // Apparently drawPixel() is actually expected to set the pixel and not draw it.
719 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
720 if (mScaling != 1 && isUnitTestRunning())
722 // On HiDPI displays, draw a square on the entire non-hidpi "pixel" when running unittests,
723 // since tests often require precise pixel drawing.
724 paint.setStrokeWidth(1); // this will be scaled by mScaling
725 paint.setStrokeCap(SkPaint::kSquare_Cap);
727 getDrawCanvas()->drawPoint(toSkX(nX), toSkY(nY), paint);
728 postDraw();
731 void SkiaSalGraphicsImpl::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
732 tools::Long nY2)
734 if (!moLineColor)
735 return;
736 preDraw();
737 SAL_INFO("vcl.skia.trace", "drawline(" << this << "): " << Point(nX1, nY1) << "->"
738 << Point(nX2, nY2) << ":" << *moLineColor);
739 addUpdateRegion(SkRect::MakeLTRB(nX1, nY1, nX2, nY2).makeSorted());
740 SkPaint paint = makeLinePaint();
741 paint.setAntiAlias(mParent.getAntiAlias());
742 if (mScaling != 1 && isUnitTestRunning())
744 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
745 // smoothing that would confuse unittests.
746 paint.setStrokeWidth(1); // this will be scaled by mScaling
747 paint.setStrokeCap(SkPaint::kSquare_Cap);
749 getDrawCanvas()->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
750 postDraw();
753 void SkiaSalGraphicsImpl::privateDrawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
754 tools::Long nHeight, double fTransparency,
755 bool blockAA)
757 preDraw();
758 SAL_INFO("vcl.skia.trace", "privatedrawrect("
759 << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight)
760 << ":" << dumpOptionalColor(moLineColor) << ":"
761 << dumpOptionalColor(moFillColor) << ":" << fTransparency);
762 addUpdateRegion(SkRect::MakeXYWH(nX, nY, nWidth, nHeight));
763 SkCanvas* canvas = getDrawCanvas();
764 if (moFillColor)
766 SkPaint paint = makeFillPaint(fTransparency);
767 paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
768 // HACK: If the polygon is just a line, it still should be drawn. But when filling
769 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
770 if (!moLineColor && SkSize::Make(nWidth, nHeight).isEmpty())
771 paint.setStyle(SkPaint::kStroke_Style);
772 canvas->drawIRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), paint);
774 if (moLineColor && moLineColor != moFillColor) // otherwise handled by fill
776 SkPaint paint = makeLinePaint(fTransparency);
777 paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
778 if (mScaling != 1 && isUnitTestRunning())
780 // On HiDPI displays, do not draw just a hairline but instead a full-width "pixel" when running unittests,
781 // since tests often require precise pixel drawing.
782 paint.setStrokeWidth(1); // this will be scaled by mScaling
783 paint.setStrokeCap(SkPaint::kSquare_Cap);
785 // The obnoxious "-1 DrawRect()" hack that I don't understand the purpose of (and I'm not sure
786 // if anybody does), but without it some cases do not work. The max() is needed because Skia
787 // will not draw anything if width or height is 0.
788 canvas->drawRect(SkRect::MakeXYWH(toSkX(nX), toSkY(nY),
789 std::max(tools::Long(1), nWidth - 1),
790 std::max(tools::Long(1), nHeight - 1)),
791 paint);
793 postDraw();
796 void SkiaSalGraphicsImpl::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
797 tools::Long nHeight)
799 privateDrawAlphaRect(nX, nY, nWidth, nHeight, 0.0, true);
802 void SkiaSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
804 basegfx::B2DPolygon aPolygon;
805 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
806 for (sal_uInt32 i = 1; i < nPoints; ++i)
807 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
808 aPolygon.setClosed(false);
810 drawPolyLine(basegfx::B2DHomMatrix(), aPolygon, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
811 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false);
814 void SkiaSalGraphicsImpl::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
816 basegfx::B2DPolygon aPolygon;
817 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
818 for (sal_uInt32 i = 1; i < nPoints; ++i)
819 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
821 drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPolygon), 0.0);
824 void SkiaSalGraphicsImpl::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
825 const Point** pPtAry)
827 basegfx::B2DPolyPolygon aPolyPolygon;
828 for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
830 sal_uInt32 nPoints = pPoints[nPolygon];
831 if (nPoints)
833 const Point* pSubPoints = pPtAry[nPolygon];
834 basegfx::B2DPolygon aPolygon;
835 aPolygon.append(basegfx::B2DPoint(pSubPoints->getX(), pSubPoints->getY()), nPoints);
836 for (sal_uInt32 i = 1; i < nPoints; ++i)
837 aPolygon.setB2DPoint(i,
838 basegfx::B2DPoint(pSubPoints[i].getX(), pSubPoints[i].getY()));
840 aPolyPolygon.append(aPolygon);
844 drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon, 0.0);
847 bool SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
848 const basegfx::B2DPolyPolygon& rPolyPolygon,
849 double fTransparency)
851 const bool bHasFill(moFillColor.has_value());
852 const bool bHasLine(moLineColor.has_value());
854 if (rPolyPolygon.count() == 0 || !(bHasFill || bHasLine) || fTransparency < 0.0
855 || fTransparency >= 1.0)
856 return true;
858 basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
859 aPolyPolygon.transform(rObjectToDevice);
861 SAL_INFO("vcl.skia.trace", "drawpolypolygon(" << this << "): " << aPolyPolygon << ":"
862 << dumpOptionalColor(moLineColor) << ":"
863 << dumpOptionalColor(moFillColor));
865 if (delayDrawPolyPolygon(aPolyPolygon, fTransparency))
867 scheduleFlush();
868 return true;
871 performDrawPolyPolygon(aPolyPolygon, fTransparency, mParent.getAntiAlias());
872 return true;
875 void SkiaSalGraphicsImpl::performDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
876 double fTransparency, bool useAA)
878 preDraw();
880 SkPath polygonPath;
881 bool hasOnlyOrthogonal = true;
882 addPolyPolygonToPath(aPolyPolygon, polygonPath, &hasOnlyOrthogonal);
883 polygonPath.setFillType(SkPathFillType::kEvenOdd);
884 addUpdateRegion(polygonPath.getBounds());
886 // For lines we use toSkX()/toSkY() in order to pass centers of pixels to Skia,
887 // as that leads to better results with floating-point coordinates
888 // (e.g. https://bugs.chromium.org/p/skia/issues/detail?id=9611).
889 // But that means that we generally need to use it also for areas, so that they
890 // line up properly if used together (tdf#134346).
891 // On the other hand, with AA enabled and rectangular areas, this leads to fuzzy
892 // edges (tdf#137329). But since rectangular areas line up perfectly to pixels
893 // everywhere, it shouldn't be necessary to do this for them.
894 // So if AA is enabled, avoid this fixup for rectangular areas.
895 if (!useAA || !hasOnlyOrthogonal)
897 // We normally use pixel at their center positions, but slightly off (see toSkX/Y()).
898 // With AA lines that "slightly off" causes tiny changes of color, making some tests
899 // fail. Since moving AA-ed line slightly to a side doesn't cause any real visual
900 // difference, just place exactly at the center. tdf#134346
901 const SkScalar posFix = useAA ? toSkXYFix : 0;
902 polygonPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
904 if (moFillColor)
906 SkPaint aPaint = makeFillPaint(fTransparency);
907 aPaint.setAntiAlias(useAA);
908 // HACK: If the polygon is just a line, it still should be drawn. But when filling
909 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
910 if (!moLineColor && polygonPath.getBounds().isEmpty())
911 aPaint.setStyle(SkPaint::kStroke_Style);
912 getDrawCanvas()->drawPath(polygonPath, aPaint);
914 if (moLineColor && moLineColor != moFillColor) // otherwise handled by fill
916 SkPaint aPaint = makeLinePaint(fTransparency);
917 aPaint.setAntiAlias(useAA);
918 getDrawCanvas()->drawPath(polygonPath, aPaint);
920 postDraw();
923 namespace
925 struct LessThan
927 bool operator()(const basegfx::B2DPoint& point1, const basegfx::B2DPoint& point2) const
929 if (basegfx::fTools::equal(point1.getX(), point2.getX()))
930 return basegfx::fTools::less(point1.getY(), point2.getY());
931 return basegfx::fTools::less(point1.getX(), point2.getX());
934 } // namespace
936 bool SkiaSalGraphicsImpl::delayDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
937 double fTransparency)
939 // There is some code that needlessly subdivides areas into adjacent rectangles,
940 // but Skia doesn't line them up perfectly if AA is enabled (e.g. Cairo, Qt5 do,
941 // but Skia devs claim it's working as intended
942 // https://groups.google.com/d/msg/skia-discuss/NlKpD2X_5uc/Vuwd-kyYBwAJ).
943 // An example is tdf#133016, which triggers SvgStyleAttributes::add_stroke()
944 // implementing a line stroke as a bunch of polygons instead of just one, and
945 // SvgLinearAtomPrimitive2D::create2DDecomposition() creates a gradient
946 // as a series of polygons of gradually changing color. Those places should be
947 // changed, but try to merge those split polygons back into the original one,
948 // where the needlessly created edges causing problems will not exist.
949 // This means drawing of such polygons needs to be delayed, so that they can
950 // be possibly merged with the next one.
951 // Merge only polygons of the same properties (color, etc.), so the gradient problem
952 // actually isn't handled here.
954 // Only AA polygons need merging, because they do not line up well because of the AA of the edges.
955 if (!mParent.getAntiAlias())
956 return false;
957 // Only filled polygons without an outline are problematic.
958 if (!moFillColor || moLineColor)
959 return false;
960 // Merge only simple polygons, real polypolygons most likely aren't needlessly split,
961 // so they do not need joining.
962 if (aPolyPolygon.count() != 1)
963 return false;
964 // If the polygon is not closed, it doesn't mark an area to be filled.
965 if (!aPolyPolygon.isClosed())
966 return false;
967 // If a polygon does not contain a straight line, i.e. it's all curves, then do not merge.
968 // First of all that's even more expensive, and second it's very unlikely that it's a polygon
969 // split into more polygons.
970 if (!polygonContainsLine(aPolyPolygon))
971 return false;
973 if (mLastPolyPolygonInfo.polygons.size() != 0
974 && (mLastPolyPolygonInfo.transparency != fTransparency
975 || !mLastPolyPolygonInfo.bounds.overlaps(aPolyPolygon.getB2DRange())))
977 checkPendingDrawing(); // Cannot be parts of the same larger polygon, draw the last and reset.
979 if (!mLastPolyPolygonInfo.polygons.empty())
981 assert(aPolyPolygon.count() == 1);
982 assert(mLastPolyPolygonInfo.polygons.back().count() == 1);
983 // Check if the new and the previous polygon share at least one point. If not, then they
984 // cannot be adjacent polygons, so there's no point in trying to merge them.
985 bool sharePoint = false;
986 const basegfx::B2DPolygon& poly1 = aPolyPolygon.getB2DPolygon(0);
987 const basegfx::B2DPolygon& poly2 = mLastPolyPolygonInfo.polygons.back().getB2DPolygon(0);
988 o3tl::sorted_vector<basegfx::B2DPoint, LessThan> poly1Points; // for O(n log n)
989 poly1Points.reserve(poly1.count());
990 for (sal_uInt32 i = 0; i < poly1.count(); ++i)
991 poly1Points.insert(poly1.getB2DPoint(i));
992 for (sal_uInt32 i = 0; i < poly2.count(); ++i)
993 if (poly1Points.find(poly2.getB2DPoint(i)) != poly1Points.end())
995 sharePoint = true;
996 break;
998 if (!sharePoint)
999 checkPendingDrawing(); // Draw the previous one and reset.
1001 // Collect the polygons that can be possibly merged. Do the merging only once at the end,
1002 // because it's not a cheap operation.
1003 mLastPolyPolygonInfo.polygons.push_back(aPolyPolygon);
1004 mLastPolyPolygonInfo.bounds.expand(aPolyPolygon.getB2DRange());
1005 mLastPolyPolygonInfo.transparency = fTransparency;
1006 return true;
1009 // Tdf#140848 - basegfx::utils::mergeToSinglePolyPolygon() seems to have rounding
1010 // errors that sometimes cause it to merge incorrectly.
1011 static void roundPolygonPoints(basegfx::B2DPolyPolygon& polyPolygon)
1013 for (basegfx::B2DPolygon& polygon : polyPolygon)
1015 polygon.makeUnique();
1016 for (sal_uInt32 i = 0; i < polygon.count(); ++i)
1017 polygon.setB2DPoint(i, basegfx::B2DPoint(basegfx::fround(polygon.getB2DPoint(i))));
1018 // Control points are saved as vectors relative to points, so hopefully
1019 // there's no need to round those.
1023 void SkiaSalGraphicsImpl::checkPendingDrawing()
1025 if (mLastPolyPolygonInfo.polygons.size() != 0)
1026 { // Flush any pending polygon drawing.
1027 basegfx::B2DPolyPolygonVector polygons;
1028 std::swap(polygons, mLastPolyPolygonInfo.polygons);
1029 double transparency = mLastPolyPolygonInfo.transparency;
1030 mLastPolyPolygonInfo.bounds.reset();
1031 if (polygons.size() == 1)
1032 performDrawPolyPolygon(polygons.front(), transparency, true);
1033 else
1035 for (basegfx::B2DPolyPolygon& p : polygons)
1036 roundPolygonPoints(p);
1037 performDrawPolyPolygon(basegfx::utils::mergeToSinglePolyPolygon(polygons), transparency,
1038 true);
1043 bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
1044 const basegfx::B2DPolygon& rPolyLine, double fTransparency,
1045 double fLineWidth, const std::vector<double>* pStroke,
1046 basegfx::B2DLineJoin eLineJoin,
1047 css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
1048 bool bPixelSnapHairline)
1050 if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0 || !moLineColor)
1052 return true;
1055 preDraw();
1056 SAL_INFO("vcl.skia.trace",
1057 "drawpolyline(" << this << "): " << rPolyLine << ":" << *moLineColor);
1059 // Adjust line width for object-to-device scale.
1060 fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
1061 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
1062 // smoothing that would confuse unittests.
1063 if (fLineWidth == 0 && mScaling != 1 && isUnitTestRunning())
1064 fLineWidth = 1; // this will be scaled by mScaling
1066 // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
1067 basegfx::B2DPolygon aPolyLine(rPolyLine);
1068 aPolyLine.transform(rObjectToDevice);
1069 if (bPixelSnapHairline)
1071 aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);
1074 SkPaint aPaint = makeLinePaint(fTransparency);
1076 switch (eLineJoin)
1078 case basegfx::B2DLineJoin::Bevel:
1079 aPaint.setStrokeJoin(SkPaint::kBevel_Join);
1080 break;
1081 case basegfx::B2DLineJoin::Round:
1082 aPaint.setStrokeJoin(SkPaint::kRound_Join);
1083 break;
1084 case basegfx::B2DLineJoin::NONE:
1085 break;
1086 case basegfx::B2DLineJoin::Miter:
1087 aPaint.setStrokeJoin(SkPaint::kMiter_Join);
1088 // convert miter minimum angle to miter limit
1089 aPaint.setStrokeMiter(1.0 / std::sin(fMiterMinimumAngle / 2.0));
1090 break;
1093 switch (eLineCap)
1095 case css::drawing::LineCap_ROUND:
1096 aPaint.setStrokeCap(SkPaint::kRound_Cap);
1097 break;
1098 case css::drawing::LineCap_SQUARE:
1099 aPaint.setStrokeCap(SkPaint::kSquare_Cap);
1100 break;
1101 default: // css::drawing::LineCap_BUTT:
1102 aPaint.setStrokeCap(SkPaint::kButt_Cap);
1103 break;
1106 aPaint.setStrokeWidth(fLineWidth);
1107 aPaint.setAntiAlias(mParent.getAntiAlias());
1108 // See the tdf#134346 comment above.
1109 const SkScalar posFix = mParent.getAntiAlias() ? toSkXYFix : 0;
1111 if (pStroke && std::accumulate(pStroke->begin(), pStroke->end(), 0.0) != 0)
1113 std::vector<SkScalar> intervals;
1114 // Transform size by the matrix.
1115 for (double stroke : *pStroke)
1116 intervals.push_back((rObjectToDevice * basegfx::B2DVector(stroke, 0)).getLength());
1117 aPaint.setPathEffect(SkDashPathEffect::Make(intervals.data(), intervals.size(), 0));
1120 // Skia does not support basegfx::B2DLineJoin::NONE, so in that case batch only if lines
1121 // are not wider than a pixel.
1122 if (eLineJoin != basegfx::B2DLineJoin::NONE || fLineWidth <= 1.0)
1124 SkPath aPath;
1125 aPath.incReserve(aPolyLine.count() * 3); // because cubicTo is 3 elements
1126 addPolygonToPath(aPolyLine, aPath);
1127 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1128 addUpdateRegion(aPath.getBounds());
1129 getDrawCanvas()->drawPath(aPath, aPaint);
1131 else
1133 sal_uInt32 nPoints = aPolyLine.count();
1134 bool bClosed = aPolyLine.isClosed();
1135 bool bHasCurves = aPolyLine.areControlPointsUsed();
1136 for (sal_uInt32 j = 0; j < nPoints; ++j)
1138 SkPath aPath;
1139 aPath.incReserve(2 * 3); // because cubicTo is 3 elements
1140 addPolygonToPath(aPolyLine, aPath, j, j + 1, nPoints, bClosed, bHasCurves);
1141 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1142 addUpdateRegion(aPath.getBounds());
1143 getDrawCanvas()->drawPath(aPath, aPaint);
1147 postDraw();
1149 return true;
1152 bool SkiaSalGraphicsImpl::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*)
1154 return false;
1157 bool SkiaSalGraphicsImpl::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*)
1159 return false;
1162 bool SkiaSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*, const Point* const*,
1163 const PolyFlags* const*)
1165 return false;
1168 void SkiaSalGraphicsImpl::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
1169 tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
1170 bool /*bWindowInvalidate*/)
1172 if (nDestX == nSrcX && nDestY == nSrcY)
1173 return;
1174 preDraw();
1175 SAL_INFO("vcl.skia.trace", "copyarea("
1176 << this << "): " << Point(nSrcX, nSrcY) << "->"
1177 << SkIRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
1178 // Using SkSurface::draw() should be more efficient, but it's too buggy.
1179 SalTwoRect rPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
1180 privateCopyBits(rPosAry, this);
1181 postDraw();
1184 void SkiaSalGraphicsImpl::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
1186 preDraw();
1187 SkiaSalGraphicsImpl* src;
1188 if (pSrcGraphics)
1190 assert(dynamic_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl()));
1191 src = static_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl());
1192 src->checkSurface();
1193 src->flushDrawing();
1195 else
1197 src = this;
1198 assert(mXorMode == XorMode::None);
1200 auto srcDebug = [&]() -> std::string {
1201 if (src == this)
1202 return "(self)";
1203 else
1205 std::ostringstream stream;
1206 stream << "(" << src << ")";
1207 return stream.str();
1210 SAL_INFO("vcl.skia.trace", "copybits(" << this << "): " << srcDebug() << ": " << rPosAry);
1211 privateCopyBits(rPosAry, src);
1212 postDraw();
1215 void SkiaSalGraphicsImpl::privateCopyBits(const SalTwoRect& rPosAry, SkiaSalGraphicsImpl* src)
1217 assert(mXorMode == XorMode::None);
1218 addUpdateRegion(SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1219 rPosAry.mnDestHeight));
1220 SkPaint paint;
1221 paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
1222 SkIRect srcRect = SkIRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth,
1223 rPosAry.mnSrcHeight);
1224 SkRect destRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1225 rPosAry.mnDestHeight);
1227 if (!SkIRect::Intersects(srcRect, SkIRect::MakeWH(src->GetWidth(), src->GetHeight()))
1228 || !SkRect::Intersects(destRect, SkRect::MakeWH(GetWidth(), GetHeight())))
1229 return;
1231 if (src == this)
1233 // Copy-to-self means that we'd take a snapshot, which would refcount the data,
1234 // and then drawing would result in copy in write, copying the entire surface.
1235 // Try to copy less by making a snapshot of only what is needed.
1236 // A complication here is that drawImageRect() can handle coordinates outside
1237 // of surface fine, but makeImageSnapshot() will crop to the surface area,
1238 // so do that manually here in order to adjust also destination rectangle.
1239 if (srcRect.x() < 0 || srcRect.y() < 0)
1241 destRect.fLeft += -srcRect.x();
1242 destRect.fTop += -srcRect.y();
1243 srcRect.adjust(-srcRect.x(), -srcRect.y(), 0, 0);
1245 // Note that right() and bottom() are not inclusive (are outside of the rect).
1246 if (srcRect.right() - 1 > GetWidth() || srcRect.bottom() - 1 > GetHeight())
1248 destRect.fRight += GetWidth() - srcRect.right();
1249 destRect.fBottom += GetHeight() - srcRect.bottom();
1250 srcRect.adjust(0, 0, GetWidth() - srcRect.right(), GetHeight() - srcRect.bottom());
1252 // Scaling for source coordinates must be done manually.
1253 if (src->mScaling != 1)
1254 srcRect = scaleRect(srcRect, src->mScaling);
1255 sk_sp<SkImage> image = makeCheckedImageSnapshot(src->mSurface, srcRect);
1256 srcRect.offset(-srcRect.x(), -srcRect.y());
1257 getDrawCanvas()->drawImageRect(image, SkRect::Make(srcRect), destRect,
1258 makeSamplingOptions(rPosAry, mScaling, src->mScaling),
1259 &paint, SkCanvas::kFast_SrcRectConstraint);
1261 else
1263 // Scaling for source coordinates must be done manually.
1264 if (src->mScaling != 1)
1265 srcRect = scaleRect(srcRect, src->mScaling);
1266 // Do not use makeImageSnapshot(rect), as that one may make a needless data copy.
1267 getDrawCanvas()->drawImageRect(makeCheckedImageSnapshot(src->mSurface),
1268 SkRect::Make(srcRect), destRect,
1269 makeSamplingOptions(rPosAry, mScaling, src->mScaling),
1270 &paint, SkCanvas::kFast_SrcRectConstraint);
1274 bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect& rPosAry, const SalBitmap& rBitmap)
1276 if (checkInvalidSourceOrDestination(rPosAry))
1277 return false;
1279 assert(dynamic_cast<const SkiaSalBitmap*>(&rBitmap));
1280 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rBitmap);
1281 // This is used by VirtualDevice in the alpha mode for the "alpha" layer which
1282 // is actually one-minus-alpha (opacity). Therefore white=0xff=transparent,
1283 // black=0x00=opaque. So the result is transparent only if both the inputs
1284 // are transparent. Since for blending operations white=1.0 and black=0.0,
1285 // kMultiply should handle exactly that (transparent*transparent=transparent,
1286 // opaque*transparent=opaque). And guessing from the "floor" in TYPE_BLEND in opengl's
1287 // combinedTextureFragmentShader.glsl, the layer is not even alpha values but
1288 // simply yes-or-no mask.
1289 // See also blendAlphaBitmap().
1290 if (rSkiaBitmap.IsFullyOpaqueAsAlpha())
1292 // Optimization. If the bitmap means fully opaque, it's all zero's. In CPU
1293 // mode it should be faster to just copy instead of SkBlendMode::kMultiply.
1294 drawBitmap(rPosAry, rSkiaBitmap);
1296 else
1297 drawBitmap(rPosAry, rSkiaBitmap, SkBlendMode::kMultiply);
1298 return true;
1301 bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry,
1302 const SalBitmap& rSourceBitmap,
1303 const SalBitmap& rMaskBitmap,
1304 const SalBitmap& rAlphaBitmap)
1306 // tdf#156361 use slow blending path if alpha mask blending is diabled
1307 // SkiaSalGraphicsImpl::blendBitmap() fails unexpectedly in the following
1308 // cases so return false and use the non-Skia alpha mask blending code:
1309 // - Unexpected white areas when running a slideshow or printing:
1310 // https://bugs.documentfoundation.org/attachment.cgi?id=188447
1311 // - Unexpected scaling of bitmap and/or alpha mask when exporting to PDF:
1312 // https://bugs.documentfoundation.org/attachment.cgi?id=188498
1313 if (!SkiaHelper::isAlphaMaskBlendingEnabled())
1314 return false;
1316 if (checkInvalidSourceOrDestination(rPosAry))
1317 return false;
1319 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1320 assert(dynamic_cast<const SkiaSalBitmap*>(&rMaskBitmap));
1321 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1322 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1323 const SkiaSalBitmap& rSkiaMaskBitmap = static_cast<const SkiaSalBitmap&>(rMaskBitmap);
1324 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1326 if (rSkiaMaskBitmap.IsFullyOpaqueAsAlpha())
1328 // Optimization. If the mask of the bitmap to be blended means it's actually opaque,
1329 // just draw the bitmap directly (that's what the math below will result in).
1330 drawBitmap(rPosAry, rSkiaSourceBitmap);
1331 return true;
1333 // This was originally implemented for the OpenGL drawing method and it is poorly documented.
1334 // The source and mask bitmaps are the usual data and alpha bitmaps, and 'alpha'
1335 // is the "alpha" layer of the VirtualDevice (the alpha in VirtualDevice is also stored
1336 // as a separate bitmap). Now if I understand it correctly these two alpha masks first need
1337 // to be combined into the actual alpha mask to be used. The formula for TYPE_BLEND
1338 // in opengl's combinedTextureFragmentShader.glsl is
1339 // "result_alpha = 1.0 - (1.0 - floor(alpha)) * mask".
1340 // See also blendBitmap().
1342 SkSamplingOptions samplingOptions = makeSamplingOptions(rPosAry, mScaling);
1343 // First do the "( 1 - alpha ) * mask"
1344 // (no idea how to do "floor", but hopefully not needed in practice).
1345 sk_sp<SkShader> shaderAlpha
1346 = SkShaders::Blend(SkBlendMode::kDstOut, rSkiaMaskBitmap.GetAlphaSkShader(samplingOptions),
1347 rSkiaAlphaBitmap.GetAlphaSkShader(samplingOptions));
1348 // And now draw the bitmap with "1 - x", where x is the "( 1 - alpha ) * mask".
1349 sk_sp<SkShader> shader = SkShaders::Blend(SkBlendMode::kSrcOut, shaderAlpha,
1350 rSkiaSourceBitmap.GetSkShader(samplingOptions));
1351 drawShader(rPosAry, shader);
1352 return true;
1355 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
1357 if (checkInvalidSourceOrDestination(rPosAry))
1358 return;
1360 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1361 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1363 drawBitmap(rPosAry, rSkiaSourceBitmap);
1366 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1367 const SalBitmap& rMaskBitmap)
1369 drawAlphaBitmap(rPosAry, rSalBitmap, rMaskBitmap);
1372 void SkiaSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1373 Color nMaskColor)
1375 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1376 const SkiaSalBitmap& skiaBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1377 drawShader(
1378 rPosAry,
1379 SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1380 SkShaders::Color(toSkColor(nMaskColor)),
1381 skiaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
1384 std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(tools::Long nX, tools::Long nY,
1385 tools::Long nWidth, tools::Long nHeight)
1387 SkiaZone zone;
1388 checkSurface();
1389 SAL_INFO("vcl.skia.trace",
1390 "getbitmap(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight));
1391 flushDrawing();
1392 // TODO makeImageSnapshot(rect) may copy the data, which may be a waste if this is used
1393 // e.g. for VirtualDevice's lame alpha blending, in which case the image will eventually end up
1394 // in blendAlphaBitmap(), where we could simply use the proper rect of the image.
1395 sk_sp<SkImage> image = makeCheckedImageSnapshot(
1396 mSurface, scaleRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), mScaling));
1397 std::shared_ptr<SkiaSalBitmap> bitmap = std::make_shared<SkiaSalBitmap>(image);
1398 // If the surface is scaled for HiDPI, the bitmap needs to be scaled down, otherwise
1399 // it would have incorrect size from the API point of view. The DirectImage::Yes handling
1400 // in mergeCacheBitmaps() should access the original unscaled bitmap data to avoid
1401 // pointless scaling back and forth.
1402 if (mScaling != 1)
1404 if (!isUnitTestRunning())
1405 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, goodScalingQuality());
1406 else
1408 // Some tests require exact pixel values and would be confused by smooth-scaling.
1409 // And some draw something smooth and not smooth-scaling there would break the checks.
1410 if (isUnitTestRunning("BackendTest__testDrawHaflEllipseAAWithPolyLineB2D_")
1411 || isUnitTestRunning("BackendTest__testDrawRectAAWithLine_")
1412 || isUnitTestRunning("GraphicsRenderTest__testDrawRectAAWithLine"))
1414 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, goodScalingQuality());
1416 else
1417 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::NearestNeighbor);
1420 return bitmap;
1423 Color SkiaSalGraphicsImpl::getPixel(tools::Long nX, tools::Long nY)
1425 SkiaZone zone;
1426 checkSurface();
1427 SAL_INFO("vcl.skia.trace", "getpixel(" << this << "): " << Point(nX, nY));
1428 flushDrawing();
1429 // This is presumably slow, but getPixel() should be generally used only by unit tests.
1430 SkBitmap bitmap;
1431 if (!bitmap.tryAllocN32Pixels(mSurface->width(), mSurface->height()))
1432 abort();
1433 if (!mSurface->readPixels(bitmap, 0, 0))
1434 abort();
1435 return fromSkColor(bitmap.getColor(nX * mScaling, nY * mScaling));
1438 void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFlags)
1440 preDraw();
1441 SAL_INFO("vcl.skia.trace", "invert(" << this << "): " << rPoly << ":" << int(eFlags));
1442 assert(mXorMode == XorMode::None);
1443 SkPath aPath;
1444 aPath.incReserve(rPoly.count());
1445 addPolygonToPath(rPoly, aPath);
1446 aPath.setFillType(SkPathFillType::kEvenOdd);
1447 addUpdateRegion(aPath.getBounds());
1448 SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
1449 SkPaint aPaint;
1450 // There's no blend mode for inverting as such, but kExclusion is 's + d - 2*s*d',
1451 // so with d = 1.0 (all channels) it becomes effectively '1 - s', i.e. inverted color.
1452 aPaint.setBlendMode(SkBlendMode::kExclusion);
1453 aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
1454 // TrackFrame just inverts a dashed path around the polygon
1455 if (eFlags == SalInvert::TrackFrame)
1457 // TrackFrame is not supposed to paint outside of the polygon (usually rectangle),
1458 // but wider stroke width usually results in that, so ensure the requirement
1459 // by clipping.
1460 getDrawCanvas()->clipRect(aPath.getBounds(), SkClipOp::kIntersect, false);
1461 aPaint.setStrokeWidth(2);
1462 constexpr float intervals[] = { 4.0f, 4.0f };
1463 aPaint.setStyle(SkPaint::kStroke_Style);
1464 aPaint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
1466 else
1468 aPaint.setStyle(SkPaint::kFill_Style);
1470 // N50 inverts in checker pattern
1471 if (eFlags == SalInvert::N50)
1473 // This creates 2x2 checker pattern bitmap
1474 // TODO Use createSkSurface() and cache the image
1475 SkBitmap aBitmap;
1476 aBitmap.allocN32Pixels(2, 2);
1477 const SkPMColor white = SkPreMultiplyARGB(0xFF, 0xFF, 0xFF, 0xFF);
1478 const SkPMColor black = SkPreMultiplyARGB(0xFF, 0x00, 0x00, 0x00);
1479 SkPMColor* scanline;
1480 scanline = aBitmap.getAddr32(0, 0);
1481 *scanline++ = white;
1482 *scanline++ = black;
1483 scanline = aBitmap.getAddr32(0, 1);
1484 *scanline++ = black;
1485 *scanline++ = white;
1486 aBitmap.setImmutable();
1487 // The bitmap is repeated in both directions the checker pattern is as big
1488 // as the polygon (usually rectangle)
1489 aPaint.setShader(
1490 aBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()));
1493 getDrawCanvas()->drawPath(aPath, aPaint);
1494 postDraw();
1497 void SkiaSalGraphicsImpl::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
1498 tools::Long nHeight, SalInvert eFlags)
1500 basegfx::B2DRectangle aRectangle(nX, nY, nX + nWidth, nY + nHeight);
1501 auto aRect = basegfx::utils::createPolygonFromRect(aRectangle);
1502 invert(aRect, eFlags);
1505 void SkiaSalGraphicsImpl::invert(sal_uInt32 nPoints, const Point* pPointArray, SalInvert eFlags)
1507 basegfx::B2DPolygon aPolygon;
1508 aPolygon.append(basegfx::B2DPoint(pPointArray[0].getX(), pPointArray[0].getY()), nPoints);
1509 for (sal_uInt32 i = 1; i < nPoints; ++i)
1511 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPointArray[i].getX(), pPointArray[i].getY()));
1513 aPolygon.setClosed(true);
1515 invert(aPolygon, eFlags);
1518 bool SkiaSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*,
1519 sal_uInt32)
1521 return false;
1524 // Create SkImage from a bitmap and possibly an alpha mask (the usual VCL one-minus-alpha),
1525 // with the given target size. Result will be possibly cached, unless disabled.
1526 // Especially in raster mode scaling and alpha blending may be expensive if done repeatedly.
1527 sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitmap,
1528 const SkiaSalBitmap* alphaBitmap,
1529 const Size& targetSize)
1531 if (alphaBitmap)
1532 assert(bitmap.GetSize() == alphaBitmap->GetSize());
1534 if (targetSize.IsEmpty())
1535 return {};
1536 if (alphaBitmap && alphaBitmap->IsFullyOpaqueAsAlpha())
1537 alphaBitmap = nullptr; // the alpha can be ignored
1538 if (bitmap.PreferSkShader() && (!alphaBitmap || alphaBitmap->PreferSkShader()))
1539 return {};
1541 // If the bitmap has SkImage that matches the required size, try to use it, even
1542 // if it doesn't match bitmap.GetSize(). This can happen with delayed scaling.
1543 // This will catch cases such as some code pre-scaling the bitmap, which would make GetSkImage()
1544 // scale, changing GetImageKey() in the process so we'd have to re-cache, and then we'd need
1545 // to scale again in this function.
1546 bool bitmapReady = false;
1547 bool alphaBitmapReady = false;
1548 if (const sk_sp<SkImage>& image = bitmap.GetSkImage(DirectImage::Yes))
1550 assert(!bitmap.PreferSkShader());
1551 if (imageSize(image) == targetSize)
1552 bitmapReady = true;
1554 // If the image usable and there's no alpha, then it matches exactly what's wanted.
1555 if (bitmapReady && !alphaBitmap)
1556 return bitmap.GetSkImage(DirectImage::Yes);
1557 if (alphaBitmap)
1559 if (!alphaBitmap->GetAlphaSkImage(DirectImage::Yes)
1560 && alphaBitmap->GetSkImage(DirectImage::Yes)
1561 && imageSize(alphaBitmap->GetSkImage(DirectImage::Yes)) == targetSize)
1563 // There's a usable non-alpha image, try to convert it to alpha.
1564 assert(!alphaBitmap->PreferSkShader());
1565 const_cast<SkiaSalBitmap*>(alphaBitmap)->TryDirectConvertToAlphaNoScaling();
1567 if (const sk_sp<SkImage>& image = alphaBitmap->GetAlphaSkImage(DirectImage::Yes))
1569 assert(!alphaBitmap->PreferSkShader());
1570 if (imageSize(image) == targetSize)
1571 alphaBitmapReady = true;
1575 if (bitmapReady && (!alphaBitmap || alphaBitmapReady))
1577 // Try to find a cached image based on the already existing images.
1578 OString key = makeCachedImageKey(bitmap, alphaBitmap, targetSize, DirectImage::Yes,
1579 DirectImage::Yes);
1580 if (sk_sp<SkImage> image = findCachedImage(key))
1582 assert(imageSize(image) == targetSize);
1583 return image;
1587 // Probably not much point in caching of just doing a copy.
1588 if (alphaBitmap == nullptr && targetSize == bitmap.GetSize())
1589 return {};
1590 // Image too small to be worth caching if not scaling.
1591 if (targetSize == bitmap.GetSize() && targetSize.Width() < 100 && targetSize.Height() < 100)
1592 return {};
1593 // GPU-accelerated drawing with SkShader should be fast enough to not need caching.
1594 if (isGPU())
1596 // tdf#140925: But if this is such an extensive downscaling that caching the result
1597 // would noticeably reduce amount of data processed by the GPU on repeated usage, do it.
1598 int reduceRatio = bitmap.GetSize().Width() * bitmap.GetSize().Height() / targetSize.Width()
1599 / targetSize.Height();
1600 if (reduceRatio < 10)
1601 return {};
1603 // Do not cache the result if it would take most of the cache and thus get evicted soon.
1604 if (targetSize.Width() * targetSize.Height() * 4 > maxImageCacheSize() * 0.7)
1605 return {};
1607 // Use ready direct image if they are both available, now even the size doesn't matter
1608 // (we'll scale as necessary and it's better to scale from the original). Require only
1609 // that they are the same size, or that one prefers a shader or doesn't exist
1610 // (i.e. avoid two images of different size).
1611 bitmapReady = bitmap.GetSkImage(DirectImage::Yes) != nullptr;
1612 alphaBitmapReady = alphaBitmap && alphaBitmap->GetAlphaSkImage(DirectImage::Yes) != nullptr;
1613 if (bitmapReady && alphaBitmap && !alphaBitmapReady && !alphaBitmap->PreferSkShader())
1614 bitmapReady = false;
1615 if (alphaBitmapReady && !bitmapReady && bitmap.PreferSkShader())
1616 alphaBitmapReady = false;
1618 DirectImage bitmapType = bitmapReady ? DirectImage::Yes : DirectImage::No;
1619 DirectImage alphaBitmapType = alphaBitmapReady ? DirectImage::Yes : DirectImage::No;
1621 // Try to find a cached result, this time after possible delayed scaling.
1622 OString key = makeCachedImageKey(bitmap, alphaBitmap, targetSize, bitmapType, alphaBitmapType);
1623 if (sk_sp<SkImage> image = findCachedImage(key))
1625 assert(imageSize(image) == targetSize);
1626 return image;
1629 // In some cases (tdf#134237) the target size may be very large. In that case it's
1630 // better to rely on Skia to clip and draw only the necessary, rather than prepare
1631 // a very large image only to not use most of it. Do this only after checking whether
1632 // the image is already cached, since it might have been already cached in a previous
1633 // call that had the draw area large enough to be seen as worth caching.
1634 const Size drawAreaSize = mClipRegion.GetBoundRect().GetSize() * mScaling;
1635 if (targetSize.Width() > drawAreaSize.Width() || targetSize.Height() > drawAreaSize.Height())
1637 // This is a bit tricky. The condition above just checks that at least a part of the resulting
1638 // image will not be used (it's larger then our drawing area). But this may often happen
1639 // when just scrolling a document with a large image, where the caching may very well be worth it.
1640 // Since the problem is mainly the cost of upscaling and then the size of the resulting bitmap,
1641 // compute a ratio of how much this is going to be scaled up, how much this is larger than
1642 // the drawing area, and then refuse to cache if it's too much.
1643 const double upscaleRatio
1644 = std::max(1.0, 1.0 * targetSize.Width() / bitmap.GetSize().Width()
1645 * targetSize.Height() / bitmap.GetSize().Height());
1646 const double oversizeRatio = 1.0 * targetSize.Width() / drawAreaSize.Width()
1647 * targetSize.Height() / drawAreaSize.Height();
1648 const double ratio = upscaleRatio * oversizeRatio;
1649 if (ratio > 4)
1651 SAL_INFO("vcl.skia.trace", "mergecachebitmaps("
1652 << this << "): not caching, ratio:" << ratio << ", "
1653 << bitmap.GetSize() << "->" << targetSize << " in "
1654 << drawAreaSize);
1655 return {};
1659 Size sourceSize;
1660 if (bitmapReady)
1661 sourceSize = imageSize(bitmap.GetSkImage(DirectImage::Yes));
1662 else if (alphaBitmapReady)
1663 sourceSize = imageSize(alphaBitmap->GetAlphaSkImage(DirectImage::Yes));
1664 else
1665 sourceSize = bitmap.GetSize();
1667 // Generate a new result and cache it.
1668 sk_sp<SkSurface> tmpSurface
1669 = createSkSurface(targetSize, alphaBitmap ? kPremul_SkAlphaType : bitmap.alphaType());
1670 if (!tmpSurface)
1671 return nullptr;
1672 SkCanvas* canvas = tmpSurface->getCanvas();
1673 SkAutoCanvasRestore autoRestore(canvas, true);
1674 SkPaint paint;
1675 SkSamplingOptions samplingOptions;
1676 if (targetSize != sourceSize)
1678 SkMatrix matrix;
1679 matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / sourceSize.Width());
1680 matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / sourceSize.Height());
1681 canvas->concat(matrix);
1682 if (!isUnitTestRunning()) // unittests want exact pixel values
1683 samplingOptions = makeSamplingOptions(matrix, 1);
1685 if (alphaBitmap != nullptr)
1687 canvas->clear(SK_ColorTRANSPARENT);
1688 paint.setShader(
1689 SkShaders::Blend(SkBlendMode::kDstOut, bitmap.GetSkShader(samplingOptions, bitmapType),
1690 alphaBitmap->GetAlphaSkShader(samplingOptions, alphaBitmapType)));
1691 canvas->drawPaint(paint);
1693 else if (bitmap.PreferSkShader())
1695 paint.setShader(bitmap.GetSkShader(samplingOptions, bitmapType));
1696 canvas->drawPaint(paint);
1698 else
1699 canvas->drawImage(bitmap.GetSkImage(bitmapType), 0, 0, samplingOptions, &paint);
1700 if (isGPU())
1701 SAL_INFO("vcl.skia.trace", "mergecachebitmaps(" << this << "): caching GPU downscaling:"
1702 << bitmap.GetSize() << "->" << targetSize);
1703 sk_sp<SkImage> image = makeCheckedImageSnapshot(tmpSurface);
1704 addCachedImage(key, image);
1705 return image;
1708 OString SkiaSalGraphicsImpl::makeCachedImageKey(const SkiaSalBitmap& bitmap,
1709 const SkiaSalBitmap* alphaBitmap,
1710 const Size& targetSize, DirectImage bitmapType,
1711 DirectImage alphaBitmapType)
1713 OString key = OString::number(targetSize.Width()) + "x" + OString::number(targetSize.Height())
1714 + "_" + bitmap.GetImageKey(bitmapType);
1715 if (alphaBitmap)
1716 key += "_" + alphaBitmap->GetAlphaImageKey(alphaBitmapType);
1717 return key;
1720 bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
1721 const SalBitmap& rAlphaBitmap)
1723 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1724 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1725 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1726 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1727 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1728 // alpha blending or scaling.
1729 SalTwoRect imagePosAry(rPosAry);
1730 Size imageSize = rSourceBitmap.GetSize();
1731 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1732 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1733 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1734 && rPosAry.mnSrcWidth == rSourceBitmap.GetSize().Width()
1735 && rPosAry.mnSrcHeight == rSourceBitmap.GetSize().Height())
1737 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1738 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1739 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1741 sk_sp<SkImage> image
1742 = mergeCacheBitmaps(rSkiaSourceBitmap, &rSkiaAlphaBitmap, imageSize * mScaling);
1743 if (image)
1744 drawImage(imagePosAry, image, mScaling);
1745 else if (rSkiaAlphaBitmap.IsFullyOpaqueAsAlpha()
1746 && !rSkiaSourceBitmap.PreferSkShader()) // alpha can be ignored
1747 drawBitmap(rPosAry, rSkiaSourceBitmap);
1748 else
1749 drawShader(rPosAry,
1750 SkShaders::Blend(
1751 SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1752 rSkiaSourceBitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)),
1753 rSkiaAlphaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
1754 return true;
1757 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBitmap& bitmap,
1758 SkBlendMode blendMode)
1760 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1761 // scaling.
1762 SalTwoRect imagePosAry(rPosAry);
1763 Size imageSize = bitmap.GetSize();
1764 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1765 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1766 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1767 && rPosAry.mnSrcWidth == bitmap.GetSize().Width()
1768 && rPosAry.mnSrcHeight == bitmap.GetSize().Height())
1770 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1771 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1772 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1774 sk_sp<SkImage> image = mergeCacheBitmaps(bitmap, nullptr, imageSize * mScaling);
1775 if (image)
1776 drawImage(imagePosAry, image, mScaling, blendMode);
1777 else if (bitmap.PreferSkShader())
1778 drawShader(rPosAry, bitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)), blendMode);
1779 else
1780 drawImage(rPosAry, bitmap.GetSkImage(), 1, blendMode);
1783 void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage,
1784 int srcScaling, SkBlendMode eBlendMode)
1786 SkRect aSourceRect
1787 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1788 if (srcScaling != 1)
1789 aSourceRect = scaleRect(aSourceRect, srcScaling);
1790 SkRect aDestinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY,
1791 rPosAry.mnDestWidth, rPosAry.mnDestHeight);
1793 SkPaint aPaint = makeBitmapPaint();
1794 aPaint.setBlendMode(eBlendMode);
1796 preDraw();
1797 SAL_INFO("vcl.skia.trace",
1798 "drawimage(" << this << "): " << rPosAry << ":" << SkBlendMode_Name(eBlendMode));
1799 addUpdateRegion(aDestinationRect);
1800 getDrawCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect,
1801 makeSamplingOptions(rPosAry, mScaling, srcScaling), &aPaint,
1802 SkCanvas::kFast_SrcRectConstraint);
1803 ++pendingOperationsToFlush; // tdf#136369
1804 postDraw();
1807 // SkShader can be used to merge multiple bitmaps with appropriate blend modes (e.g. when
1808 // merging a bitmap with its alpha mask).
1809 void SkiaSalGraphicsImpl::drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader,
1810 SkBlendMode blendMode)
1812 preDraw();
1813 SAL_INFO("vcl.skia.trace", "drawshader(" << this << "): " << rPosAry);
1814 SkRect destinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1815 rPosAry.mnDestHeight);
1816 addUpdateRegion(destinationRect);
1817 SkPaint paint = makeBitmapPaint();
1818 paint.setBlendMode(blendMode);
1819 paint.setShader(shader);
1820 SkCanvas* canvas = getDrawCanvas();
1821 // Scaling needs to be done explicitly using a matrix.
1822 SkAutoCanvasRestore autoRestore(canvas, true);
1823 SkMatrix matrix = SkMatrix::Translate(rPosAry.mnDestX, rPosAry.mnDestY)
1824 * SkMatrix::Scale(1.0 * rPosAry.mnDestWidth / rPosAry.mnSrcWidth,
1825 1.0 * rPosAry.mnDestHeight / rPosAry.mnSrcHeight)
1826 * SkMatrix::Translate(-rPosAry.mnSrcX, -rPosAry.mnSrcY);
1827 #ifndef NDEBUG
1828 // Handle floating point imprecisions, round p1 to 2 decimal places.
1829 auto compareRounded = [](const SkPoint& p1, const SkPoint& p2) {
1830 return rtl::math::round(p1.x(), 2) == p2.x() && rtl::math::round(p1.y(), 2) == p2.y();
1832 #endif
1833 assert(compareRounded(matrix.mapXY(rPosAry.mnSrcX, rPosAry.mnSrcY),
1834 SkPoint::Make(rPosAry.mnDestX, rPosAry.mnDestY)));
1835 assert(compareRounded(
1836 matrix.mapXY(rPosAry.mnSrcX + rPosAry.mnSrcWidth, rPosAry.mnSrcY + rPosAry.mnSrcHeight),
1837 SkPoint::Make(rPosAry.mnDestX + rPosAry.mnDestWidth,
1838 rPosAry.mnDestY + rPosAry.mnDestHeight)));
1839 canvas->concat(matrix);
1840 SkRect sourceRect
1841 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1842 canvas->drawRect(sourceRect, paint);
1843 postDraw();
1846 bool SkiaSalGraphicsImpl::hasFastDrawTransformedBitmap() const
1848 // Return true even in raster mode, even that way Skia is faster than e.g. GraphicObject
1849 // trying to handle stuff manually.
1850 return true;
1853 // Whether applying matrix needs image smoothing for the transformation.
1854 static bool matrixNeedsHighQuality(const SkMatrix& matrix)
1856 if (matrix.isIdentity())
1857 return false;
1858 if (matrix.isScaleTranslate())
1860 if (abs(matrix.getScaleX()) == 1 && abs(matrix.getScaleY()) == 1)
1861 return false; // Only at most flipping and keeping the size.
1862 return true;
1864 assert(!matrix.hasPerspective()); // we do not use this
1865 if (matrix.getScaleX() == 0 && matrix.getScaleY() == 0)
1867 // Rotating 90 or 270 degrees while keeping the size.
1868 if ((matrix.getSkewX() == 1 && matrix.getSkewY() == -1)
1869 || (matrix.getSkewX() == -1 && matrix.getSkewY() == 1))
1870 return false;
1872 return true;
1875 namespace SkiaTests
1877 bool matrixNeedsHighQuality(const SkMatrix& matrix) { return ::matrixNeedsHighQuality(matrix); }
1880 bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
1881 const basegfx::B2DPoint& rX,
1882 const basegfx::B2DPoint& rY,
1883 const SalBitmap& rSourceBitmap,
1884 const SalBitmap* pAlphaBitmap, double fAlpha)
1886 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1887 assert(!pAlphaBitmap || dynamic_cast<const SkiaSalBitmap*>(pAlphaBitmap));
1889 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1890 const SkiaSalBitmap* pSkiaAlphaBitmap = static_cast<const SkiaSalBitmap*>(pAlphaBitmap);
1892 if (pSkiaAlphaBitmap && pSkiaAlphaBitmap->IsFullyOpaqueAsAlpha())
1893 pSkiaAlphaBitmap = nullptr; // the alpha can be ignored
1895 // Setup the image transformation,
1896 // using the rNull, rX, rY points as destinations for the (0,0), (Width,0), (0,Height) source points.
1897 const basegfx::B2DVector aXRel = rX - rNull;
1898 const basegfx::B2DVector aYRel = rY - rNull;
1900 preDraw();
1901 SAL_INFO("vcl.skia.trace", "drawtransformedbitmap(" << this << "): " << rSourceBitmap.GetSize()
1902 << " " << rNull << ":" << rX << ":" << rY);
1904 addUpdateRegion(SkRect::MakeWH(GetWidth(), GetHeight())); // can't tell, use whole area
1905 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1906 // alpha blending or scaling.
1907 // The extra fAlpha blending is not cached, with the assumption that it usually gradually changes
1908 // for each invocation.
1909 // Pass size * mScaling to mergeCacheBitmaps() so that it prepares the size that will be needed
1910 // after the mScaling-scaling matrix, but otherwise calculate everything else using the VCL coordinates.
1911 Size imageSize(round(aXRel.getLength()), round(aYRel.getLength()));
1912 sk_sp<SkImage> imageToDraw
1913 = mergeCacheBitmaps(rSkiaBitmap, pSkiaAlphaBitmap, imageSize * mScaling);
1914 if (imageToDraw)
1916 SkMatrix matrix;
1917 // Round sizes for scaling, so that sub-pixel differences don't
1918 // trigger unnecessary scaling. Image has already been scaled
1919 // by mergeCacheBitmaps() and we shouldn't scale here again
1920 // unless the drawing is also skewed.
1921 matrix.set(SkMatrix::kMScaleX, round(aXRel.getX()) / imageSize.Width());
1922 matrix.set(SkMatrix::kMScaleY, round(aYRel.getY()) / imageSize.Height());
1923 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / imageSize.Width());
1924 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / imageSize.Height());
1925 matrix.set(SkMatrix::kMTransX, rNull.getX());
1926 matrix.set(SkMatrix::kMTransY, rNull.getY());
1927 SkCanvas* canvas = getDrawCanvas();
1928 SkAutoCanvasRestore autoRestore(canvas, true);
1929 canvas->concat(matrix);
1930 SkSamplingOptions samplingOptions;
1931 // If the matrix changes geometry, we need to smooth-scale. If there's mScaling,
1932 // that's already been handled by mergeCacheBitmaps().
1933 if (matrixNeedsHighQuality(matrix))
1934 samplingOptions = makeSamplingOptions(matrix, 1);
1935 if (fAlpha == 1.0)
1937 // Specify sizes to scale the image size back if needed (because of mScaling).
1938 SkRect dstRect = SkRect::MakeWH(imageSize.Width(), imageSize.Height());
1939 SkRect srcRect = SkRect::MakeWH(imageToDraw->width(), imageToDraw->height());
1940 SkPaint paint = makeBitmapPaint();
1941 canvas->drawImageRect(imageToDraw, srcRect, dstRect, samplingOptions, &paint,
1942 SkCanvas::kFast_SrcRectConstraint);
1944 else
1946 SkPaint paint = makeBitmapPaint();
1947 // Scale the image size back if needed.
1948 SkMatrix scale = SkMatrix::Scale(1.0 / mScaling, 1.0 / mScaling);
1949 paint.setShader(SkShaders::Blend(
1950 SkBlendMode::kDstIn, imageToDraw->makeShader(samplingOptions, &scale),
1951 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
1952 canvas->drawRect(SkRect::MakeWH(imageSize.Width(), imageSize.Height()), paint);
1955 else
1957 SkMatrix matrix;
1958 const Size aSize = rSourceBitmap.GetSize();
1959 matrix.set(SkMatrix::kMScaleX, aXRel.getX() / aSize.Width());
1960 matrix.set(SkMatrix::kMScaleY, aYRel.getY() / aSize.Height());
1961 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / aSize.Width());
1962 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / aSize.Height());
1963 matrix.set(SkMatrix::kMTransX, rNull.getX());
1964 matrix.set(SkMatrix::kMTransY, rNull.getY());
1965 SkCanvas* canvas = getDrawCanvas();
1966 SkAutoCanvasRestore autoRestore(canvas, true);
1967 canvas->concat(matrix);
1968 SkSamplingOptions samplingOptions;
1969 if (matrixNeedsHighQuality(matrix) || (mScaling != 1 && !isUnitTestRunning()))
1970 samplingOptions = makeSamplingOptions(matrix, mScaling);
1971 if (pSkiaAlphaBitmap)
1973 SkPaint paint = makeBitmapPaint();
1974 paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1975 rSkiaBitmap.GetSkShader(samplingOptions),
1976 pSkiaAlphaBitmap->GetAlphaSkShader(samplingOptions)));
1977 if (fAlpha != 1.0)
1978 paint.setShader(
1979 SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
1980 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
1981 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
1983 else if (rSkiaBitmap.PreferSkShader() || fAlpha != 1.0)
1985 SkPaint paint = makeBitmapPaint();
1986 paint.setShader(rSkiaBitmap.GetSkShader(samplingOptions));
1987 if (fAlpha != 1.0)
1988 paint.setShader(
1989 SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
1990 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
1991 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
1993 else
1995 SkPaint paint = makeBitmapPaint();
1996 canvas->drawImage(rSkiaBitmap.GetSkImage(), 0, 0, samplingOptions, &paint);
1999 postDraw();
2000 return true;
2003 bool SkiaSalGraphicsImpl::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
2004 tools::Long nHeight, sal_uInt8 nTransparency)
2006 privateDrawAlphaRect(nX, nY, nWidth, nHeight, nTransparency / 100.0);
2007 return true;
2010 bool SkiaSalGraphicsImpl::drawGradient(const tools::PolyPolygon& rPolyPolygon,
2011 const Gradient& rGradient)
2013 if (rGradient.GetStyle() != css::awt::GradientStyle_LINEAR
2014 && rGradient.GetStyle() != css::awt::GradientStyle_AXIAL
2015 && rGradient.GetStyle() != css::awt::GradientStyle_RADIAL)
2016 return false; // unsupported
2017 if (rGradient.GetSteps() != 0)
2018 return false; // We can't tell Skia how many colors to use in the gradient.
2019 preDraw();
2020 SAL_INFO("vcl.skia.trace", "drawgradient(" << this << "): " << rPolyPolygon.getB2DPolyPolygon()
2021 << ":" << static_cast<int>(rGradient.GetStyle()));
2022 tools::Rectangle boundRect(rPolyPolygon.GetBoundRect());
2023 if (boundRect.IsEmpty())
2024 return true;
2025 SkPath path;
2026 if (rPolyPolygon.IsRect())
2028 // Rect->Polygon conversion loses the right and bottom edge, fix that.
2029 path.addRect(SkRect::MakeXYWH(boundRect.getX(), boundRect.getY(), boundRect.GetWidth(),
2030 boundRect.GetHeight()));
2031 boundRect.AdjustRight(1);
2032 boundRect.AdjustBottom(1);
2034 else
2035 addPolyPolygonToPath(rPolyPolygon.getB2DPolyPolygon(), path);
2036 path.setFillType(SkPathFillType::kEvenOdd);
2037 addUpdateRegion(path.getBounds());
2039 Gradient aGradient(rGradient);
2040 tools::Rectangle aBoundRect;
2041 Point aCenter;
2042 aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10);
2043 aGradient.GetBoundRect(boundRect, aBoundRect, aCenter);
2045 SkColor startColor
2046 = toSkColorWithIntensity(rGradient.GetStartColor(), rGradient.GetStartIntensity());
2047 SkColor endColor = toSkColorWithIntensity(rGradient.GetEndColor(), rGradient.GetEndIntensity());
2049 sk_sp<SkShader> shader;
2050 if (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR)
2052 tools::Polygon aPoly(aBoundRect);
2053 aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
2054 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
2055 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
2056 SkColor colors[2] = { startColor, endColor };
2057 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
2058 shader = SkGradientShader::MakeLinear(points, colors, pos, 2, SkTileMode::kClamp);
2060 else if (rGradient.GetStyle() == css::awt::GradientStyle_AXIAL)
2062 tools::Polygon aPoly(aBoundRect);
2063 aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
2064 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
2065 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
2066 SkColor colors[3] = { endColor, startColor, endColor };
2067 SkScalar border = SkDoubleToScalar(aGradient.GetBorder() / 100.0);
2068 SkScalar pos[3] = { std::min<SkScalar>(border * 0.5f, 0.5f), 0.5f,
2069 std::max<SkScalar>(1 - border * 0.5f, 0.5f) };
2070 shader = SkGradientShader::MakeLinear(points, colors, pos, 3, SkTileMode::kClamp);
2072 else
2074 // Move the center by (-1,-1) (the default VCL algorithm is a bit off-center that way,
2075 // Skia is the opposite way).
2076 SkPoint center = SkPoint::Make(toSkX(aCenter.X()) - 1, toSkY(aCenter.Y()) - 1);
2077 SkScalar radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
2078 SkColor colors[2] = { endColor, startColor };
2079 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
2080 shader = SkGradientShader::MakeRadial(center, radius, colors, pos, 2, SkTileMode::kClamp);
2083 SkPaint paint = makeGradientPaint();
2084 paint.setAntiAlias(mParent.getAntiAlias());
2085 paint.setShader(shader);
2086 getDrawCanvas()->drawPath(path, paint);
2087 postDraw();
2088 return true;
2091 bool SkiaSalGraphicsImpl::implDrawGradient(const basegfx::B2DPolyPolygon& rPolyPolygon,
2092 const SalGradient& rGradient)
2094 preDraw();
2095 SAL_INFO("vcl.skia.trace",
2096 "impldrawgradient(" << this << "): " << rPolyPolygon << ":" << rGradient.maPoint1
2097 << "->" << rGradient.maPoint2 << ":" << rGradient.maStops.size());
2099 SkPath path;
2100 addPolyPolygonToPath(rPolyPolygon, path);
2101 path.setFillType(SkPathFillType::kEvenOdd);
2102 addUpdateRegion(path.getBounds());
2104 SkPoint points[2]
2105 = { SkPoint::Make(toSkX(rGradient.maPoint1.getX()), toSkY(rGradient.maPoint1.getY())),
2106 SkPoint::Make(toSkX(rGradient.maPoint2.getX()), toSkY(rGradient.maPoint2.getY())) };
2107 std::vector<SkColor> colors;
2108 std::vector<SkScalar> pos;
2109 for (const SalGradientStop& stop : rGradient.maStops)
2111 colors.emplace_back(toSkColor(stop.maColor));
2112 pos.emplace_back(stop.mfOffset);
2114 sk_sp<SkShader> shader = SkGradientShader::MakeLinear(points, colors.data(), pos.data(),
2115 colors.size(), SkTileMode::kDecal);
2116 SkPaint paint = makeGradientPaint();
2117 paint.setAntiAlias(mParent.getAntiAlias());
2118 paint.setShader(shader);
2119 getDrawCanvas()->drawPath(path, paint);
2120 postDraw();
2121 return true;
2124 static double toRadian(Degree10 degree10th) { return toRadians(3600_deg10 - degree10th); }
2125 static double toCos(Degree10 degree10th) { return SkScalarCos(toRadian(degree10th)); }
2126 static double toSin(Degree10 degree10th) { return SkScalarSin(toRadian(degree10th)); }
2128 void SkiaSalGraphicsImpl::drawGenericLayout(const GenericSalLayout& layout, Color textColor,
2129 const SkFont& font, const SkFont& verticalFont)
2131 SkiaZone zone;
2132 std::vector<SkGlyphID> glyphIds;
2133 std::vector<SkRSXform> glyphForms;
2134 std::vector<bool> verticals;
2135 glyphIds.reserve(256);
2136 glyphForms.reserve(256);
2137 verticals.reserve(256);
2138 DevicePoint aPos;
2139 const GlyphItem* pGlyph;
2140 int nStart = 0;
2141 while (layout.GetNextGlyph(&pGlyph, aPos, nStart))
2143 glyphIds.push_back(pGlyph->glyphId());
2144 Degree10 angle = layout.GetOrientation();
2145 if (pGlyph->IsVertical())
2146 angle += 900_deg10;
2147 SkRSXform form = SkRSXform::Make(toCos(angle), toSin(angle), aPos.getX(), aPos.getY());
2148 glyphForms.emplace_back(std::move(form));
2149 verticals.emplace_back(pGlyph->IsVertical());
2151 if (glyphIds.empty())
2152 return;
2154 preDraw();
2155 auto getBoundRect = [&layout]() {
2156 tools::Rectangle rect;
2157 layout.GetBoundRect(rect);
2158 return rect;
2160 SAL_INFO("vcl.skia.trace", "drawtextblob(" << this << "): " << getBoundRect() << ", "
2161 << glyphIds.size() << " glyphs, " << textColor);
2163 // Vertical glyphs need a different font, so split drawing into runs that each
2164 // draw only consecutive horizontal or vertical glyphs.
2165 std::vector<bool>::const_iterator pos = verticals.cbegin();
2166 std::vector<bool>::const_iterator end = verticals.cend();
2167 while (pos != end)
2169 bool verticalRun = *pos;
2170 std::vector<bool>::const_iterator rangeEnd = std::find(pos + 1, end, !verticalRun);
2171 size_t index = pos - verticals.cbegin();
2172 size_t count = rangeEnd - pos;
2173 sk_sp<SkTextBlob> textBlob = SkTextBlob::MakeFromRSXform(
2174 glyphIds.data() + index, count * sizeof(SkGlyphID), glyphForms.data() + index,
2175 verticalRun ? verticalFont : font, SkTextEncoding::kGlyphID);
2176 addUpdateRegion(textBlob->bounds());
2177 SkPaint paint = makeTextPaint(textColor);
2178 getDrawCanvas()->drawTextBlob(textBlob, 0, 0, paint);
2179 pos = rangeEnd;
2181 postDraw();
2184 bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
2186 switch (eType)
2188 case OutDevSupportType::B2DDraw:
2189 case OutDevSupportType::TransparentRect:
2190 return true;
2191 default:
2192 return false;
2196 static int getScaling()
2198 // It makes sense to support the debugging flag on all platforms
2199 // for unittests purpose, even if the actual windows cannot do it.
2200 if (const char* env = getenv("SAL_FORCE_HIDPI_SCALING"))
2201 return atoi(env);
2202 return 1;
2205 int SkiaSalGraphicsImpl::getWindowScaling() const
2207 static const int scaling = getScaling();
2208 return scaling;
2211 void SkiaSalGraphicsImpl::dump(const char* file) const
2213 assert(mSurface.get());
2214 SkiaHelper::dump(mSurface, file);
2217 void SkiaSalGraphicsImpl::windowBackingPropertiesChanged()
2219 if (mInWindowBackingPropertiesChanged || !isGPU())
2220 return;
2222 mInWindowBackingPropertiesChanged = true;
2223 performFlush();
2224 mInWindowBackingPropertiesChanged = false;
2227 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */