move code to helper functions
[LibreOffice.git] / vcl / skia / gdiimpl.cxx
blob6f326d6c5aa877f68080ce49a38798635fc43250
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <skia/gdiimpl.hxx>
22 #include <salgdi.hxx>
23 #include <skia/salbmp.hxx>
24 #include <vcl/idle.hxx>
25 #include <vcl/svapp.hxx>
26 #include <vcl/lazydelete.hxx>
27 #include <vcl/gradient.hxx>
28 #include <vcl/skia/SkiaHelper.hxx>
29 #include <skia/utils.hxx>
30 #include <skia/zone.hxx>
32 #include <SkCanvas.h>
33 #include <SkGradientShader.h>
34 #include <SkPath.h>
35 #include <SkRegion.h>
36 #include <SkDashPathEffect.h>
37 #include <GrBackendSurface.h>
38 #include <SkTextBlob.h>
39 #include <SkRSXform.h>
41 #include <numeric>
42 #include <basegfx/polygon/b2dpolygontools.hxx>
43 #include <basegfx/polygon/b2dpolypolygontools.hxx>
44 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
45 #include <o3tl/sorted_vector.hxx>
46 #include <rtl/math.hxx>
48 using namespace SkiaHelper;
50 namespace
52 // Create Skia Path from B2DPolygon
53 // Note that polygons generally have the complication that when used
54 // for area (fill) operations they usually miss the right-most and
55 // bottom-most line of pixels of the bounding rectangle (see
56 // https://lists.freedesktop.org/archives/libreoffice/2019-November/083709.html).
57 // So be careful with rectangle->polygon conversions (generally avoid them).
58 void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath, sal_uInt32 nFirstIndex,
59 sal_uInt32 nLastIndex, const sal_uInt32 nPointCount, const bool bClosePath,
60 const bool bHasCurves, bool* hasOnlyOrthogonal = nullptr)
62 assert(nFirstIndex < nPointCount);
63 assert(nLastIndex <= nPointCount);
65 if (nPointCount <= 1)
66 return;
68 bool bFirst = true;
69 sal_uInt32 nPreviousIndex = nFirstIndex == 0 ? nPointCount - 1 : nFirstIndex - 1;
70 basegfx::B2DPoint aPreviousPoint = rPolygon.getB2DPoint(nPreviousIndex);
72 for (sal_uInt32 nIndex = nFirstIndex; nIndex <= nLastIndex; nIndex++)
74 if (nIndex == nPointCount && !bClosePath)
75 continue;
77 // Make sure we loop the last point to first point
78 sal_uInt32 nCurrentIndex = nIndex % nPointCount;
79 basegfx::B2DPoint aCurrentPoint = rPolygon.getB2DPoint(nCurrentIndex);
81 if (bFirst)
83 rPath.moveTo(aCurrentPoint.getX(), aCurrentPoint.getY());
84 bFirst = false;
86 else if (!bHasCurves)
88 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY());
89 // If asked for, check whether the polygon has a line that is not
90 // strictly horizontal or vertical.
91 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
92 && aCurrentPoint.getY() != aPreviousPoint.getY())
93 *hasOnlyOrthogonal = false;
95 else
97 basegfx::B2DPoint aPreviousControlPoint = rPolygon.getNextControlPoint(nPreviousIndex);
98 basegfx::B2DPoint aCurrentControlPoint = rPolygon.getPrevControlPoint(nCurrentIndex);
100 if (aPreviousControlPoint.equal(aPreviousPoint)
101 && aCurrentControlPoint.equal(aCurrentPoint))
103 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY()); // a straight line
104 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
105 && aCurrentPoint.getY() != aPreviousPoint.getY())
106 *hasOnlyOrthogonal = false;
108 else
110 if (aPreviousControlPoint.equal(aPreviousPoint))
112 aPreviousControlPoint
113 = aPreviousPoint + ((aPreviousControlPoint - aCurrentPoint) * 0.0005);
115 if (aCurrentControlPoint.equal(aCurrentPoint))
117 aCurrentControlPoint
118 = aCurrentPoint + ((aCurrentControlPoint - aPreviousPoint) * 0.0005);
120 rPath.cubicTo(aPreviousControlPoint.getX(), aPreviousControlPoint.getY(),
121 aCurrentControlPoint.getX(), aCurrentControlPoint.getY(),
122 aCurrentPoint.getX(), aCurrentPoint.getY());
123 if (hasOnlyOrthogonal != nullptr)
124 *hasOnlyOrthogonal = false;
127 aPreviousPoint = aCurrentPoint;
128 nPreviousIndex = nCurrentIndex;
130 if (bClosePath && nFirstIndex == 0 && nLastIndex == nPointCount)
132 rPath.close();
136 void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath,
137 bool* hasOnlyOrthogonal = nullptr)
139 addPolygonToPath(rPolygon, rPath, 0, rPolygon.count(), rPolygon.count(), rPolygon.isClosed(),
140 rPolygon.areControlPointsUsed(), hasOnlyOrthogonal);
143 void addPolyPolygonToPath(const basegfx::B2DPolyPolygon& rPolyPolygon, SkPath& rPath,
144 bool* hasOnlyOrthogonal = nullptr)
146 const sal_uInt32 nPolygonCount(rPolyPolygon.count());
148 if (nPolygonCount == 0)
149 return;
151 sal_uInt32 nPointCount = 0;
152 for (const auto& rPolygon : rPolyPolygon)
153 nPointCount += rPolygon.count() * 3; // because cubicTo is 3 elements
154 rPath.incReserve(nPointCount);
156 for (const auto& rPolygon : rPolyPolygon)
158 addPolygonToPath(rPolygon, rPath, hasOnlyOrthogonal);
162 // Check if the given polygon contains a straight line. If not, it consists
163 // solely of curves.
164 bool polygonContainsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
166 if (!rPolyPolygon.areControlPointsUsed())
167 return true; // no curves at all
168 for (const auto& rPolygon : rPolyPolygon)
170 const sal_uInt32 nPointCount(rPolygon.count());
171 bool bFirst = true;
173 const bool bClosePath(rPolygon.isClosed());
175 sal_uInt32 nCurrentIndex = 0;
176 sal_uInt32 nPreviousIndex = nPointCount - 1;
178 basegfx::B2DPoint aCurrentPoint;
179 basegfx::B2DPoint aPreviousPoint;
181 for (sal_uInt32 nIndex = 0; nIndex <= nPointCount; nIndex++)
183 if (nIndex == nPointCount && !bClosePath)
184 continue;
186 // Make sure we loop the last point to first point
187 nCurrentIndex = nIndex % nPointCount;
188 if (bFirst)
189 bFirst = false;
190 else
192 basegfx::B2DPoint aPreviousControlPoint
193 = rPolygon.getNextControlPoint(nPreviousIndex);
194 basegfx::B2DPoint aCurrentControlPoint
195 = rPolygon.getPrevControlPoint(nCurrentIndex);
197 if (aPreviousControlPoint.equal(aPreviousPoint)
198 && aCurrentControlPoint.equal(aCurrentPoint))
200 return true; // found a straight line
203 aPreviousPoint = aCurrentPoint;
204 nPreviousIndex = nCurrentIndex;
207 return false; // no straight line found
210 // returns true if the source or destination rectangles are invalid
211 bool checkInvalidSourceOrDestination(SalTwoRect const& rPosAry)
213 return rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
214 || rPosAry.mnDestHeight <= 0;
217 } // end anonymous namespace
219 // Class that triggers flushing the backing buffer when idle.
220 class SkiaFlushIdle : public Idle
222 SkiaSalGraphicsImpl* mpGraphics;
223 #ifndef NDEBUG
224 char* debugname;
225 #endif
227 public:
228 explicit SkiaFlushIdle(SkiaSalGraphicsImpl* pGraphics)
229 : Idle(get_debug_name(pGraphics))
230 , mpGraphics(pGraphics)
232 // We don't want to be swapping before we've painted.
233 SetPriority(TaskPriority::POST_PAINT);
235 #ifndef NDEBUG
236 virtual ~SkiaFlushIdle() { free(debugname); }
237 #endif
238 const char* get_debug_name(SkiaSalGraphicsImpl* pGraphics)
240 #ifndef NDEBUG
241 // Idle keeps just a pointer, so we need to store the string
242 debugname = strdup(
243 OString("skia idle 0x" + OString::number(reinterpret_cast<sal_uIntPtr>(pGraphics), 16))
244 .getStr());
245 return debugname;
246 #else
247 (void)pGraphics;
248 return "skia idle";
249 #endif
252 virtual void Invoke() override
254 mpGraphics->performFlush();
255 Stop();
256 SetPriority(TaskPriority::HIGHEST);
260 SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvider* pProvider)
261 : mParent(rParent)
262 , mProvider(pProvider)
263 , mIsGPU(false)
264 , mLineColor(SALCOLOR_NONE)
265 , mFillColor(SALCOLOR_NONE)
266 , mXorMode(false)
267 , mFlush(new SkiaFlushIdle(this))
268 , mPendingOperationsToFlush(0)
269 , mScaling(1)
273 SkiaSalGraphicsImpl::~SkiaSalGraphicsImpl()
275 assert(!mSurface);
276 assert(!mWindowContext);
279 void SkiaSalGraphicsImpl::Init() {}
281 void SkiaSalGraphicsImpl::createSurface()
283 SkiaZone zone;
284 if (isOffscreen())
285 createOffscreenSurface();
286 else
287 createWindowSurface();
288 mClipRegion = vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight()));
289 mDirtyRect = SkIRect::MakeWH(GetWidth(), GetHeight());
290 setCanvasScalingAndClipping();
292 // We don't want to be swapping before we've painted.
293 mFlush->Stop();
294 mFlush->SetPriority(TaskPriority::POST_PAINT);
297 void SkiaSalGraphicsImpl::createWindowSurface(bool forceRaster)
299 SkiaZone zone;
300 assert(!isOffscreen());
301 assert(!mSurface);
302 createWindowSurfaceInternal(forceRaster);
303 if (!mSurface)
305 switch (renderMethodToUse())
307 case RenderVulkan:
308 SAL_WARN("vcl.skia",
309 "cannot create Vulkan GPU window surface, falling back to Raster");
310 destroySurface(); // destroys also WindowContext
311 return createWindowSurface(true); // try again
312 case RenderMetal:
313 SAL_WARN("vcl.skia",
314 "cannot create Metal GPU window surface, falling back to Raster");
315 destroySurface(); // destroys also WindowContext
316 return createWindowSurface(true); // try again
317 case RenderRaster:
318 abort(); // This should not really happen, do not even try to cope with it.
321 mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
322 #ifdef DBG_UTIL
323 prefillSurface(mSurface);
324 #endif
327 bool SkiaSalGraphicsImpl::isOffscreen() const
329 if (mProvider == nullptr || mProvider->IsOffScreen())
330 return true;
331 // HACK: Sometimes (tdf#131939, tdf#138022, tdf#140288) VCL passes us a zero-sized window,
332 // and zero size is invalid for Skia, so force offscreen surface, where we handle this.
333 if (GetWidth() <= 0 || GetHeight() <= 0)
334 return true;
335 return false;
338 void SkiaSalGraphicsImpl::createOffscreenSurface()
340 SkiaZone zone;
341 assert(isOffscreen());
342 assert(!mSurface);
343 // HACK: See isOffscreen().
344 int width = std::max(1, GetWidth());
345 int height = std::max(1, GetHeight());
346 // We need to use window scaling even for offscreen surfaces, because the common usage is rendering something
347 // into an offscreen surface and then copy it to a window, so without scaling here the result would be originally
348 // drawn without scaling and only upscaled when drawing to a window.
349 mScaling = getWindowScaling();
350 mSurface = createSkSurface(width * mScaling, height * mScaling);
351 assert(mSurface);
352 mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
355 void SkiaSalGraphicsImpl::destroySurface()
357 SkiaZone zone;
358 if (mSurface)
360 // check setClipRegion() invariant
361 assert(mSurface->getCanvas()->getSaveCount() == 3);
362 // if this fails, something forgot to use SkAutoCanvasRestore
363 assert(mSurface->getCanvas()->getTotalMatrix() == SkMatrix::Scale(mScaling, mScaling));
365 // If we use e.g. Vulkan, we must destroy the surface before the context,
366 // otherwise destroying the surface will reference the context. This is
367 // handled by calling destroySurface() before destroying the context.
368 // However we also need to flush the surface before destroying it,
369 // otherwise when destroying the context later there still could be queued
370 // commands referring to the surface data. This is probably a Skia bug,
371 // but work around it here.
372 if (mSurface)
373 mSurface->flushAndSubmit();
374 mSurface.reset();
375 mWindowContext.reset();
376 mIsGPU = false;
377 mScaling = 1;
380 void SkiaSalGraphicsImpl::performFlush()
382 SkiaZone zone;
383 flushDrawing();
384 if (mSurface)
386 if (mDirtyRect.intersect(SkIRect::MakeWH(GetWidth(), GetHeight())))
387 flushSurfaceToWindowContext();
388 mDirtyRect.setEmpty();
392 void SkiaSalGraphicsImpl::flushSurfaceToWindowContext()
394 sk_sp<SkSurface> screenSurface = mWindowContext->getBackbufferSurface();
395 if (screenSurface != mSurface)
397 // GPU-based window contexts require calling getBackbufferSurface()
398 // for every swapBuffers(), for this reason mSurface is an offscreen surface
399 // where we keep the contents (LO does not do full redraws).
400 // So here blit the surface to the window context surface and then swap it.
401 assert(isGPU()); // Raster should always draw directly to backbuffer to save copying
402 SkPaint paint;
403 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
404 // We ignore mDirtyRect here, and mSurface already is in screenSurface coordinates,
405 // so no transformation needed.
406 screenSurface->getCanvas()->drawImage(makeCheckedImageSnapshot(mSurface), 0, 0,
407 SkSamplingOptions(), &paint);
408 screenSurface->flushAndSubmit(); // Otherwise the window is not drawn sometimes.
409 mWindowContext->swapBuffers(nullptr); // Must swap the entire surface.
411 else
413 // For raster mode use directly the backbuffer surface, it's just a bitmap
414 // surface anyway, and for those there's no real requirement to call
415 // getBackbufferSurface() repeatedly. Using our own surface would duplicate
416 // memory and cost time copying pixels around.
417 assert(!isGPU());
418 SkIRect dirtyRect = mDirtyRect;
419 if (mScaling != 1) // Adjust to mSurface coordinates if needed.
420 dirtyRect = scaleRect(dirtyRect, mScaling);
421 mWindowContext->swapBuffers(&dirtyRect);
425 void SkiaSalGraphicsImpl::DeInit() { destroySurface(); }
427 void SkiaSalGraphicsImpl::preDraw()
429 assert(comphelper::SolarMutex::get()->IsCurrentThread());
430 SkiaZone::enter(); // matched in postDraw()
431 checkSurface();
432 checkPendingDrawing();
435 void SkiaSalGraphicsImpl::postDraw()
437 scheduleFlush();
438 // Skia (at least when using Vulkan) queues drawing commands and executes them only later.
439 // But tdf#136369 leads to creating and queueing many tiny bitmaps, which makes
440 // Skia slow, and may make it even run out of memory. So force a flush if such
441 // a problematic operation has been performed too many times without a flush.
442 if (mPendingOperationsToFlush > 1000)
444 mSurface->flushAndSubmit();
445 mPendingOperationsToFlush = 0;
447 SkiaZone::leave(); // matched in preDraw()
448 // If there's a problem with the GPU context, abort.
449 if (GrDirectContext* context = GrAsDirectContext(mSurface->getCanvas()->recordingContext()))
451 // Running out of memory on the GPU technically could be possibly recoverable,
452 // but we don't know the exact status of the surface (and what has or has not been drawn to it),
453 // so in practice this is unrecoverable without possible data loss.
454 if (context->oomed())
456 SAL_WARN("vcl.skia", "GPU context has run out of memory, aborting.");
457 abort();
459 // Unrecoverable problem.
460 if (context->abandoned())
462 SAL_WARN("vcl.skia", "GPU context has been abandoned, aborting.");
463 abort();
468 void SkiaSalGraphicsImpl::scheduleFlush()
470 if (!isOffscreen())
472 if (!Application::IsInExecute())
473 performFlush(); // otherwise nothing would trigger idle rendering
474 else if (!mFlush->IsActive())
475 mFlush->Start();
479 // VCL can sometimes resize us without telling us, update the surface if needed.
480 // Also create the surface on demand if it has not been created yet (it is a waste
481 // to create it in Init() if it gets recreated later anyway).
482 void SkiaSalGraphicsImpl::checkSurface()
484 if (!mSurface)
486 createSurface();
487 SAL_INFO("vcl.skia.trace",
488 "create(" << this << "): " << Size(mSurface->width(), mSurface->height()));
490 else if (GetWidth() * mScaling != mSurface->width()
491 || GetHeight() * mScaling != mSurface->height())
493 if (!avoidRecreateByResize())
495 Size oldSize(mSurface->width(), mSurface->height());
496 // Recreating a surface means that the old SkSurface contents will be lost.
497 // But if a window has been resized the windowing system may send repaint events
498 // only for changed parts and VCL would not repaint the whole area, assuming
499 // that some parts have not changed (this is what seems to cause tdf#131952).
500 // So carry over the old contents for windows, even though generally everything
501 // will be usually repainted anyway.
502 sk_sp<SkImage> snapshot;
503 if (!isOffscreen())
505 flushDrawing();
506 snapshot = makeCheckedImageSnapshot(mSurface);
509 destroySurface();
510 createSurface();
512 if (snapshot)
514 SkPaint paint;
515 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
516 // Scaling by current mScaling is active, undo that. We assume that the scaling
517 // does not change.
518 resetCanvasScalingAndClipping();
519 mSurface->getCanvas()->drawImage(snapshot, 0, 0, SkSamplingOptions(), &paint);
520 setCanvasScalingAndClipping();
522 SAL_INFO("vcl.skia.trace", "recreate(" << this << "): old " << oldSize << " new "
523 << Size(mSurface->width(), mSurface->height())
524 << " requested "
525 << Size(GetWidth(), GetHeight()));
530 bool SkiaSalGraphicsImpl::avoidRecreateByResize() const
532 // Keep the old surface if VCL sends us a broken size (see isOffscreen()).
533 if (GetWidth() == 0 || GetHeight() == 0)
534 return true;
535 return false;
538 void SkiaSalGraphicsImpl::flushDrawing()
540 if (!mSurface)
541 return;
542 checkPendingDrawing();
543 if (mXorMode)
544 applyXor();
545 mSurface->flushAndSubmit();
546 mPendingOperationsToFlush = 0;
549 void SkiaSalGraphicsImpl::setCanvasScalingAndClipping()
551 SkCanvas* canvas = mSurface->getCanvas();
552 assert(canvas->getSaveCount() == 1);
553 // If HiDPI scaling is active, simply set a scaling matrix for the canvas. This means
554 // that all painting can use VCL coordinates and they'll be automatically translated to mSurface
555 // scaled coordinates. If that is not wanted, the scale() state needs to be temporarily unset.
556 // State such as mDirtyRect and mXorRegion is not scaled, the scaling matrix applies to clipping too,
557 // and the rest needs to be handled explicitly.
558 // When reading mSurface contents there's no automatic scaling and it needs to be handled explicitly.
559 canvas->save(); // keep the original state without any scaling
560 canvas->scale(mScaling, mScaling);
562 // SkCanvas::clipRegion() can only further reduce the clip region,
563 // but we need to set the given region, which may extend it.
564 // So handle that by always having the full clip region saved on the stack
565 // and always go back to that. SkCanvas::restore() only affects the clip
566 // and the matrix.
567 canvas->save(); // keep scaled state without clipping
568 setCanvasClipRegion(canvas, mClipRegion);
571 void SkiaSalGraphicsImpl::resetCanvasScalingAndClipping()
573 SkCanvas* canvas = mSurface->getCanvas();
574 assert(canvas->getSaveCount() == 3);
575 canvas->restore(); // undo clipping
576 canvas->restore(); // undo scaling
579 bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
581 if (mClipRegion == region)
582 return true;
583 SkiaZone zone;
584 checkPendingDrawing();
585 checkSurface();
586 mClipRegion = region;
587 SAL_INFO("vcl.skia.trace", "setclipregion(" << this << "): " << region);
588 SkCanvas* canvas = mSurface->getCanvas();
589 assert(canvas->getSaveCount() == 3);
590 canvas->restore(); // undo previous clip state, see setCanvasScalingAndClipping()
591 canvas->save();
592 setCanvasClipRegion(canvas, region);
593 return true;
596 void SkiaSalGraphicsImpl::setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region)
598 SkiaZone zone;
599 SkPath path;
600 // Always use region rectangles, regardless of what the region uses internally.
601 // That's what other VCL backends do, and trying to use addPolyPolygonToPath()
602 // in case a polygon is used leads to off-by-one errors such as tdf#133208.
603 RectangleVector rectangles;
604 region.GetRegionRectangles(rectangles);
605 path.incReserve(rectangles.size() + 1);
606 for (const tools::Rectangle& rectangle : rectangles)
607 path.addRect(SkRect::MakeXYWH(rectangle.getX(), rectangle.getY(), rectangle.GetWidth(),
608 rectangle.GetHeight()));
609 path.setFillType(SkPathFillType::kEvenOdd);
610 canvas->clipPath(path);
613 void SkiaSalGraphicsImpl::ResetClipRegion()
615 setClipRegion(vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight())));
618 const vcl::Region& SkiaSalGraphicsImpl::getClipRegion() const { return mClipRegion; }
620 sal_uInt16 SkiaSalGraphicsImpl::GetBitCount() const { return 32; }
622 tools::Long SkiaSalGraphicsImpl::GetGraphicsWidth() const { return GetWidth(); }
624 void SkiaSalGraphicsImpl::SetLineColor()
626 checkPendingDrawing();
627 mLineColor = SALCOLOR_NONE;
630 void SkiaSalGraphicsImpl::SetLineColor(Color nColor)
632 checkPendingDrawing();
633 mLineColor = nColor;
636 void SkiaSalGraphicsImpl::SetFillColor()
638 checkPendingDrawing();
639 mFillColor = SALCOLOR_NONE;
642 void SkiaSalGraphicsImpl::SetFillColor(Color nColor)
644 checkPendingDrawing();
645 mFillColor = nColor;
648 void SkiaSalGraphicsImpl::SetXORMode(bool set, bool)
650 if (mXorMode == set)
651 return;
652 checkPendingDrawing();
653 SAL_INFO("vcl.skia.trace", "setxormode(" << this << "): " << set);
654 if (set)
655 mXorRegion.setEmpty();
656 else
657 applyXor();
658 mXorMode = set;
661 SkCanvas* SkiaSalGraphicsImpl::getXorCanvas()
663 SkiaZone zone;
664 assert(mXorMode);
665 // Skia does not implement xor drawing, so we need to handle it manually by redirecting
666 // to a temporary SkBitmap and then doing the xor operation on the data ourselves.
667 // There's no point in using SkSurface for GPU, we'd immediately need to get the pixels back.
668 if (!mXorCanvas)
670 // Use unpremultiplied alpha (see xor applying in applyXor()).
671 if (!mXorBitmap.tryAllocPixels(mSurface->imageInfo().makeAlphaType(kUnpremul_SkAlphaType)))
672 abort();
673 mXorBitmap.eraseARGB(0, 0, 0, 0);
674 mXorCanvas = std::make_unique<SkCanvas>(mXorBitmap);
675 if (mScaling != 1)
676 mXorCanvas->scale(mScaling, mScaling);
677 setCanvasClipRegion(mXorCanvas.get(), mClipRegion);
679 return mXorCanvas.get();
682 void SkiaSalGraphicsImpl::applyXor()
684 // Apply the result from the temporary bitmap manually. This is indeed
685 // slow, but it doesn't seem to be needed often and is optimized
686 // in each operation by extending mXorRegion with the area that should be
687 // updated.
688 assert(mXorMode);
689 if (mScaling != 1 && !mXorRegion.isEmpty())
691 // Scale mXorRegion to mSurface coordinates if needed.
692 std::vector<SkIRect> rects;
693 for (SkRegion::Iterator it(mXorRegion); !it.done(); it.next())
694 rects.push_back(scaleRect(it.rect(), mScaling));
695 mXorRegion.setRects(rects.data(), rects.size());
697 if (!mSurface || !mXorCanvas
698 || !mXorRegion.op(SkIRect::MakeXYWH(0, 0, mSurface->width(), mSurface->height()),
699 SkRegion::kIntersect_Op))
701 mXorRegion.setEmpty();
702 return;
704 SAL_INFO("vcl.skia.trace", "applyxor(" << this << "): " << mXorRegion);
705 // Copy the surface contents to another pixmap.
706 SkBitmap surfaceBitmap;
707 // Use unpremultiplied alpha format, so that we do not have to do the conversions to get
708 // the RGB and back (Skia will do it when converting, but it'll be presumably faster at it).
709 if (!surfaceBitmap.tryAllocPixels(mSurface->imageInfo().makeAlphaType(kUnpremul_SkAlphaType)))
710 abort();
711 SkPaint paint;
712 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
713 SkRect area = SkRect::Make(mXorRegion.getBounds());
715 SkCanvas canvas(surfaceBitmap);
716 canvas.drawImageRect(makeCheckedImageSnapshot(mSurface), area, area, SkSamplingOptions(),
717 &paint, SkCanvas::kFast_SrcRectConstraint);
719 // xor to surfaceBitmap
720 assert(surfaceBitmap.info().alphaType() == kUnpremul_SkAlphaType);
721 assert(mXorBitmap.info().alphaType() == kUnpremul_SkAlphaType);
722 assert(surfaceBitmap.bytesPerPixel() == 4);
723 assert(mXorBitmap.bytesPerPixel() == 4);
724 for (SkRegion::Iterator it(mXorRegion); !it.done(); it.next())
726 for (int y = it.rect().top(); y < it.rect().bottom(); ++y)
728 uint8_t* data = static_cast<uint8_t*>(surfaceBitmap.getAddr(it.rect().x(), y));
729 const uint8_t* xordata = static_cast<uint8_t*>(mXorBitmap.getAddr(it.rect().x(), y));
730 for (int x = 0; x < it.rect().width(); ++x)
732 *data++ ^= *xordata++;
733 *data++ ^= *xordata++;
734 *data++ ^= *xordata++;
735 // alpha is not xor-ed
736 data++;
737 xordata++;
741 surfaceBitmap.notifyPixelsChanged();
742 surfaceBitmap.setImmutable();
743 // Copy without any clipping or scaling.
744 resetCanvasScalingAndClipping();
745 mSurface->getCanvas()->drawImageRect(surfaceBitmap.asImage(), area, area, SkSamplingOptions(),
746 &paint, SkCanvas::kFast_SrcRectConstraint);
747 setCanvasScalingAndClipping();
748 mXorCanvas.reset();
749 mXorBitmap.reset();
750 mXorRegion.setEmpty();
753 void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor)
755 checkPendingDrawing();
756 switch (nROPColor)
758 case SalROPColor::N0:
759 mLineColor = Color(0, 0, 0);
760 break;
761 case SalROPColor::N1:
762 mLineColor = Color(0xff, 0xff, 0xff);
763 break;
764 case SalROPColor::Invert:
765 mLineColor = Color(0xff, 0xff, 0xff);
766 break;
770 void SkiaSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor)
772 checkPendingDrawing();
773 switch (nROPColor)
775 case SalROPColor::N0:
776 mFillColor = Color(0, 0, 0);
777 break;
778 case SalROPColor::N1:
779 mFillColor = Color(0xff, 0xff, 0xff);
780 break;
781 case SalROPColor::Invert:
782 mFillColor = Color(0xff, 0xff, 0xff);
783 break;
787 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY)
789 drawPixel(nX, nY, mLineColor);
792 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
794 if (nColor == SALCOLOR_NONE)
795 return;
796 preDraw();
797 SAL_INFO("vcl.skia.trace", "drawpixel(" << this << "): " << Point(nX, nY) << ":" << nColor);
798 addUpdateRegion(SkRect::MakeXYWH(nX, nY, 1, 1));
799 SkPaint paint;
800 paint.setColor(toSkColor(nColor));
801 // Apparently drawPixel() is actually expected to set the pixel and not draw it.
802 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
803 if (mScaling != 1 && isUnitTestRunning())
805 // On HiDPI displays, draw a square on the entire non-hidpi "pixel" when running unittests,
806 // since tests often require precise pixel drawing.
807 paint.setStrokeWidth(1); // this will be scaled by mScaling
808 paint.setStrokeCap(SkPaint::kSquare_Cap);
810 getDrawCanvas()->drawPoint(toSkX(nX), toSkY(nY), paint);
811 postDraw();
814 void SkiaSalGraphicsImpl::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
815 tools::Long nY2)
817 if (mLineColor == SALCOLOR_NONE)
818 return;
819 preDraw();
820 SAL_INFO("vcl.skia.trace", "drawline(" << this << "): " << Point(nX1, nY1) << "->"
821 << Point(nX2, nY2) << ":" << mLineColor);
822 addUpdateRegion(SkRect::MakeLTRB(nX1, nY1, nX2, nY2).makeSorted());
823 SkPaint paint = makeLinePaint();
824 paint.setAntiAlias(mParent.getAntiAlias());
825 if (mScaling != 1 && isUnitTestRunning())
827 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
828 // smoothing that would confuse unittests.
829 paint.setStrokeWidth(1); // this will be scaled by mScaling
830 paint.setStrokeCap(SkPaint::kSquare_Cap);
832 getDrawCanvas()->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
833 postDraw();
836 void SkiaSalGraphicsImpl::privateDrawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
837 tools::Long nHeight, double fTransparency,
838 bool blockAA)
840 preDraw();
841 SAL_INFO("vcl.skia.trace",
842 "privatedrawrect(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight)
843 << ":" << mLineColor << ":" << mFillColor << ":" << fTransparency);
844 addUpdateRegion(SkRect::MakeXYWH(nX, nY, nWidth, nHeight));
845 SkCanvas* canvas = getDrawCanvas();
846 if (mFillColor != SALCOLOR_NONE)
848 SkPaint paint = makeFillPaint(fTransparency);
849 paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
850 // HACK: If the polygon is just a line, it still should be drawn. But when filling
851 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
852 if (mLineColor == SALCOLOR_NONE && SkSize::Make(nWidth, nHeight).isEmpty())
853 paint.setStyle(SkPaint::kStroke_Style);
854 canvas->drawIRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), paint);
856 if (mLineColor != SALCOLOR_NONE)
858 SkPaint paint = makeLinePaint(fTransparency);
859 paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
860 if (mScaling != 1 && isUnitTestRunning())
862 // On HiDPI displays, do not draw just a hairline but instead a full-width "pixel" when running unittests,
863 // since tests often require precise pixel drawing.
864 paint.setStrokeWidth(1); // this will be scaled by mScaling
865 paint.setStrokeCap(SkPaint::kSquare_Cap);
867 // The obnoxious "-1 DrawRect()" hack that I don't understand the purpose of (and I'm not sure
868 // if anybody does), but without it some cases do not work. The max() is needed because Skia
869 // will not draw anything if width or height is 0.
870 canvas->drawRect(SkRect::MakeXYWH(toSkX(nX), toSkY(nY),
871 std::max(tools::Long(1), nWidth - 1),
872 std::max(tools::Long(1), nHeight - 1)),
873 paint);
875 postDraw();
878 void SkiaSalGraphicsImpl::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
879 tools::Long nHeight)
881 privateDrawAlphaRect(nX, nY, nWidth, nHeight, 0.0, true);
884 void SkiaSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
886 basegfx::B2DPolygon aPolygon;
887 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
888 for (sal_uInt32 i = 1; i < nPoints; ++i)
889 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
890 aPolygon.setClosed(false);
892 drawPolyLine(basegfx::B2DHomMatrix(), aPolygon, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
893 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false);
896 void SkiaSalGraphicsImpl::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
898 basegfx::B2DPolygon aPolygon;
899 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
900 for (sal_uInt32 i = 1; i < nPoints; ++i)
901 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
903 drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPolygon), 0.0);
906 void SkiaSalGraphicsImpl::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
907 const Point** pPtAry)
909 basegfx::B2DPolyPolygon aPolyPolygon;
910 for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
912 sal_uInt32 nPoints = pPoints[nPolygon];
913 if (nPoints)
915 const Point* pSubPoints = pPtAry[nPolygon];
916 basegfx::B2DPolygon aPolygon;
917 aPolygon.append(basegfx::B2DPoint(pSubPoints->getX(), pSubPoints->getY()), nPoints);
918 for (sal_uInt32 i = 1; i < nPoints; ++i)
919 aPolygon.setB2DPoint(i,
920 basegfx::B2DPoint(pSubPoints[i].getX(), pSubPoints[i].getY()));
922 aPolyPolygon.append(aPolygon);
926 drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon, 0.0);
929 bool SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
930 const basegfx::B2DPolyPolygon& rPolyPolygon,
931 double fTransparency)
933 const bool bHasFill(mFillColor != SALCOLOR_NONE);
934 const bool bHasLine(mLineColor != SALCOLOR_NONE);
936 if (rPolyPolygon.count() == 0 || !(bHasFill || bHasLine) || fTransparency < 0.0
937 || fTransparency >= 1.0)
938 return true;
940 basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
941 aPolyPolygon.transform(rObjectToDevice);
943 SAL_INFO("vcl.skia.trace", "drawpolypolygon(" << this << "): " << aPolyPolygon << ":"
944 << mLineColor << ":" << mFillColor);
946 if (delayDrawPolyPolygon(aPolyPolygon, fTransparency))
948 scheduleFlush();
949 return true;
952 performDrawPolyPolygon(aPolyPolygon, fTransparency, mParent.getAntiAlias());
953 return true;
956 void SkiaSalGraphicsImpl::performDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
957 double fTransparency, bool useAA)
959 preDraw();
961 SkPath polygonPath;
962 bool hasOnlyOrthogonal = true;
963 addPolyPolygonToPath(aPolyPolygon, polygonPath, &hasOnlyOrthogonal);
964 polygonPath.setFillType(SkPathFillType::kEvenOdd);
965 addUpdateRegion(polygonPath.getBounds());
967 // For lines we use toSkX()/toSkY() in order to pass centers of pixels to Skia,
968 // as that leads to better results with floating-point coordinates
969 // (e.g. https://bugs.chromium.org/p/skia/issues/detail?id=9611).
970 // But that means that we generally need to use it also for areas, so that they
971 // line up properly if used together (tdf#134346).
972 // On the other hand, with AA enabled and rectangular areas, this leads to fuzzy
973 // edges (tdf#137329). But since rectangular areas line up perfectly to pixels
974 // everywhere, it shouldn't be necessary to do this for them.
975 // So if AA is enabled, avoid this fixup for rectangular areas.
976 if (!useAA || !hasOnlyOrthogonal)
978 // We normally use pixel at their center positions, but slightly off (see toSkX/Y()).
979 // With AA lines that "slightly off" causes tiny changes of color, making some tests
980 // fail. Since moving AA-ed line slightly to a side doesn't cause any real visual
981 // difference, just place exactly at the center. tdf#134346
982 const SkScalar posFix = useAA ? toSkXYFix : 0;
983 polygonPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
985 if (mFillColor != SALCOLOR_NONE)
987 SkPaint aPaint = makeFillPaint(fTransparency);
988 aPaint.setAntiAlias(useAA);
989 // HACK: If the polygon is just a line, it still should be drawn. But when filling
990 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
991 if (mLineColor == SALCOLOR_NONE && polygonPath.getBounds().isEmpty())
992 aPaint.setStyle(SkPaint::kStroke_Style);
993 getDrawCanvas()->drawPath(polygonPath, aPaint);
995 if (mLineColor != SALCOLOR_NONE)
997 SkPaint aPaint = makeLinePaint(fTransparency);
998 aPaint.setAntiAlias(useAA);
999 getDrawCanvas()->drawPath(polygonPath, aPaint);
1001 postDraw();
1002 #if defined LINUX
1003 // WORKAROUND: The logo in the about dialog has drawing errors. This seems to happen
1004 // only on Linux (not Windows on the same machine), with both AMDGPU and Mesa,
1005 // and only when antialiasing is enabled. Flushing seems to avoid the problem.
1006 if (useAA && getVendor() == DriverBlocklist::VendorAMD)
1007 mSurface->flushAndSubmit();
1008 #endif
1011 namespace
1013 struct LessThan
1015 bool operator()(const basegfx::B2DPoint& point1, const basegfx::B2DPoint& point2) const
1017 if (basegfx::fTools::equal(point1.getX(), point2.getX()))
1018 return basegfx::fTools::less(point1.getY(), point2.getY());
1019 return basegfx::fTools::less(point1.getX(), point2.getX());
1022 } // namespace
1024 bool SkiaSalGraphicsImpl::delayDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
1025 double fTransparency)
1027 // There is some code that needlessly subdivides areas into adjacent rectangles,
1028 // but Skia doesn't line them up perfectly if AA is enabled (e.g. Cairo, Qt5 do,
1029 // but Skia devs claim it's working as intended
1030 // https://groups.google.com/d/msg/skia-discuss/NlKpD2X_5uc/Vuwd-kyYBwAJ).
1031 // An example is tdf#133016, which triggers SvgStyleAttributes::add_stroke()
1032 // implementing a line stroke as a bunch of polygons instead of just one, and
1033 // SvgLinearAtomPrimitive2D::create2DDecomposition() creates a gradient
1034 // as a series of polygons of gradually changing color. Those places should be
1035 // changed, but try to merge those split polygons back into the original one,
1036 // where the needlessly created edges causing problems will not exist.
1037 // This means drawing of such polygons needs to be delayed, so that they can
1038 // be possibly merged with the next one.
1039 // Merge only polygons of the same properties (color, etc.), so the gradient problem
1040 // actually isn't handled here.
1042 // Only AA polygons need merging, because they do not line up well because of the AA of the edges.
1043 if (!mParent.getAntiAlias())
1044 return false;
1045 // Only filled polygons without an outline are problematic.
1046 if (mFillColor == SALCOLOR_NONE || mLineColor != SALCOLOR_NONE)
1047 return false;
1048 // Merge only simple polygons, real polypolygons most likely aren't needlessly split,
1049 // so they do not need joining.
1050 if (aPolyPolygon.count() != 1)
1051 return false;
1052 // If the polygon is not closed, it doesn't mark an area to be filled.
1053 if (!aPolyPolygon.isClosed())
1054 return false;
1055 // If a polygon does not contain a straight line, i.e. it's all curves, then do not merge.
1056 // First of all that's even more expensive, and second it's very unlikely that it's a polygon
1057 // split into more polygons.
1058 if (!polygonContainsLine(aPolyPolygon))
1059 return false;
1061 if (mLastPolyPolygonInfo.polygons.size() != 0
1062 && (mLastPolyPolygonInfo.transparency != fTransparency
1063 || !mLastPolyPolygonInfo.bounds.overlaps(aPolyPolygon.getB2DRange())))
1065 checkPendingDrawing(); // Cannot be parts of the same larger polygon, draw the last and reset.
1067 if (!mLastPolyPolygonInfo.polygons.empty())
1069 assert(aPolyPolygon.count() == 1);
1070 assert(mLastPolyPolygonInfo.polygons.back().count() == 1);
1071 // Check if the new and the previous polygon share at least one point. If not, then they
1072 // cannot be adjacent polygons, so there's no point in trying to merge them.
1073 bool sharePoint = false;
1074 const basegfx::B2DPolygon& poly1 = aPolyPolygon.getB2DPolygon(0);
1075 const basegfx::B2DPolygon& poly2 = mLastPolyPolygonInfo.polygons.back().getB2DPolygon(0);
1076 o3tl::sorted_vector<basegfx::B2DPoint, LessThan> poly1Points; // for O(n log n)
1077 poly1Points.reserve(poly1.count());
1078 for (sal_uInt32 i = 0; i < poly1.count(); ++i)
1079 poly1Points.insert(poly1.getB2DPoint(i));
1080 for (sal_uInt32 i = 0; i < poly2.count(); ++i)
1081 if (poly1Points.find(poly2.getB2DPoint(i)) != poly1Points.end())
1083 sharePoint = true;
1084 break;
1086 if (!sharePoint)
1087 checkPendingDrawing(); // Draw the previous one and reset.
1089 // Collect the polygons that can be possibly merged. Do the merging only once at the end,
1090 // because it's not a cheap operation.
1091 mLastPolyPolygonInfo.polygons.push_back(aPolyPolygon);
1092 mLastPolyPolygonInfo.bounds.expand(aPolyPolygon.getB2DRange());
1093 mLastPolyPolygonInfo.transparency = fTransparency;
1094 return true;
1097 // Tdf#140848 - basegfx::utils::mergeToSinglePolyPolygon() seems to have rounding
1098 // errors that sometimes cause it to merge incorrectly.
1099 static void roundPolygonPoints(basegfx::B2DPolyPolygon& polyPolygon)
1101 for (basegfx::B2DPolygon& polygon : polyPolygon)
1103 polygon.makeUnique();
1104 for (sal_uInt32 i = 0; i < polygon.count(); ++i)
1105 polygon.setB2DPoint(i, basegfx::B2DPoint(basegfx::fround(polygon.getB2DPoint(i))));
1106 // Control points are saved as vectors relative to points, so hopefully
1107 // there's no need to round those.
1111 void SkiaSalGraphicsImpl::checkPendingDrawing()
1113 if (mLastPolyPolygonInfo.polygons.size() != 0)
1114 { // Flush any pending polygon drawing.
1115 basegfx::B2DPolyPolygonVector polygons;
1116 std::swap(polygons, mLastPolyPolygonInfo.polygons);
1117 double transparency = mLastPolyPolygonInfo.transparency;
1118 mLastPolyPolygonInfo.bounds.reset();
1119 if (polygons.size() == 1)
1120 performDrawPolyPolygon(polygons.front(), transparency, true);
1121 else
1123 for (basegfx::B2DPolyPolygon& p : polygons)
1124 roundPolygonPoints(p);
1125 performDrawPolyPolygon(basegfx::utils::mergeToSinglePolyPolygon(polygons), transparency,
1126 true);
1131 bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
1132 const basegfx::B2DPolygon& rPolyLine, double fTransparency,
1133 double fLineWidth, const std::vector<double>* pStroke,
1134 basegfx::B2DLineJoin eLineJoin,
1135 css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
1136 bool bPixelSnapHairline)
1138 if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0
1139 || mLineColor == SALCOLOR_NONE)
1141 return true;
1144 preDraw();
1145 SAL_INFO("vcl.skia.trace", "drawpolyline(" << this << "): " << rPolyLine << ":" << mLineColor);
1147 // Adjust line width for object-to-device scale.
1148 fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
1149 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
1150 // smoothing that would confuse unittests.
1151 if (fLineWidth == 0 && mScaling != 1 && isUnitTestRunning())
1152 fLineWidth = 1; // this will be scaled by mScaling
1154 // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
1155 basegfx::B2DPolygon aPolyLine(rPolyLine);
1156 aPolyLine.transform(rObjectToDevice);
1157 if (bPixelSnapHairline)
1159 aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);
1162 // Setup Line Join
1163 SkPaint::Join eSkLineJoin = SkPaint::kMiter_Join;
1164 switch (eLineJoin)
1166 case basegfx::B2DLineJoin::Bevel:
1167 eSkLineJoin = SkPaint::kBevel_Join;
1168 break;
1169 case basegfx::B2DLineJoin::Round:
1170 eSkLineJoin = SkPaint::kRound_Join;
1171 break;
1172 case basegfx::B2DLineJoin::NONE:
1173 case basegfx::B2DLineJoin::Miter:
1174 eSkLineJoin = SkPaint::kMiter_Join;
1175 break;
1178 // convert miter minimum angle to miter limit
1179 double fMiterLimit = 1.0 / std::sin(fMiterMinimumAngle / 2.0);
1181 // Setup Line Cap
1182 SkPaint::Cap eSkLineCap(SkPaint::kButt_Cap);
1184 switch (eLineCap)
1186 case css::drawing::LineCap_ROUND:
1187 eSkLineCap = SkPaint::kRound_Cap;
1188 break;
1189 case css::drawing::LineCap_SQUARE:
1190 eSkLineCap = SkPaint::kSquare_Cap;
1191 break;
1192 default: // css::drawing::LineCap_BUTT:
1193 eSkLineCap = SkPaint::kButt_Cap;
1194 break;
1197 SkPaint aPaint = makeLinePaint(fTransparency);
1198 aPaint.setStrokeCap(eSkLineCap);
1199 aPaint.setStrokeJoin(eSkLineJoin);
1200 aPaint.setStrokeMiter(fMiterLimit);
1201 aPaint.setStrokeWidth(fLineWidth);
1202 aPaint.setAntiAlias(mParent.getAntiAlias());
1203 // See the tdf#134346 comment above.
1204 const SkScalar posFix = mParent.getAntiAlias() ? toSkXYFix : 0;
1206 if (pStroke && std::accumulate(pStroke->begin(), pStroke->end(), 0.0) != 0)
1208 std::vector<SkScalar> intervals;
1209 // Transform size by the matrix.
1210 for (double stroke : *pStroke)
1211 intervals.push_back((rObjectToDevice * basegfx::B2DVector(stroke, 0)).getLength());
1212 aPaint.setPathEffect(SkDashPathEffect::Make(intervals.data(), intervals.size(), 0));
1215 // Skia does not support basegfx::B2DLineJoin::NONE, so in that case batch only if lines
1216 // are not wider than a pixel.
1217 if (eLineJoin != basegfx::B2DLineJoin::NONE || fLineWidth <= 1.0)
1219 SkPath aPath;
1220 aPath.incReserve(aPolyLine.count() * 3); // because cubicTo is 3 elements
1221 addPolygonToPath(aPolyLine, aPath);
1222 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1223 addUpdateRegion(aPath.getBounds());
1224 getDrawCanvas()->drawPath(aPath, aPaint);
1226 else
1228 sal_uInt32 nPoints = aPolyLine.count();
1229 bool bClosed = aPolyLine.isClosed();
1230 bool bHasCurves = aPolyLine.areControlPointsUsed();
1231 for (sal_uInt32 j = 0; j < nPoints; ++j)
1233 SkPath aPath;
1234 aPath.incReserve(2 * 3); // because cubicTo is 3 elements
1235 addPolygonToPath(aPolyLine, aPath, j, j + 1, nPoints, bClosed, bHasCurves);
1236 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1237 addUpdateRegion(aPath.getBounds());
1238 getDrawCanvas()->drawPath(aPath, aPaint);
1242 postDraw();
1244 return true;
1247 bool SkiaSalGraphicsImpl::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*)
1249 return false;
1252 bool SkiaSalGraphicsImpl::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*)
1254 return false;
1257 bool SkiaSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*, const Point* const*,
1258 const PolyFlags* const*)
1260 return false;
1263 void SkiaSalGraphicsImpl::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
1264 tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
1265 bool /*bWindowInvalidate*/)
1267 if (nDestX == nSrcX && nDestY == nSrcY)
1268 return;
1269 preDraw();
1270 SAL_INFO("vcl.skia.trace", "copyarea("
1271 << this << "): " << Point(nSrcX, nSrcY) << "->"
1272 << SkIRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
1273 // Using SkSurface::draw() should be more efficient, but it's too buggy.
1274 SalTwoRect rPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
1275 privateCopyBits(rPosAry, this);
1276 postDraw();
1279 void SkiaSalGraphicsImpl::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
1281 preDraw();
1282 SkiaSalGraphicsImpl* src;
1283 if (pSrcGraphics)
1285 assert(dynamic_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl()));
1286 src = static_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl());
1287 src->checkSurface();
1288 src->flushDrawing();
1290 else
1292 src = this;
1293 assert(!mXorMode);
1295 auto srcDebug = [&]() -> std::string {
1296 if (src == this)
1297 return "(self)";
1298 else
1300 std::ostringstream stream;
1301 stream << "(" << src << ")";
1302 return stream.str();
1305 SAL_INFO("vcl.skia.trace", "copybits(" << this << "): " << srcDebug() << ": " << rPosAry);
1306 privateCopyBits(rPosAry, src);
1307 postDraw();
1310 void SkiaSalGraphicsImpl::privateCopyBits(const SalTwoRect& rPosAry, SkiaSalGraphicsImpl* src)
1312 assert(!mXorMode);
1313 addUpdateRegion(SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1314 rPosAry.mnDestHeight));
1315 SkPaint paint;
1316 paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
1317 SkIRect srcRect = SkIRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth,
1318 rPosAry.mnSrcHeight);
1319 SkRect destRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1320 rPosAry.mnDestHeight);
1321 // Scaling for source coordinates must be done manually.
1322 if (src->mScaling != 1)
1323 srcRect = scaleRect(srcRect, src->mScaling);
1324 if (src == this)
1326 // Copy-to-self means that we'd take a snapshot, which would refcount the data,
1327 // and then drawing would result in copy in write, copying the entire surface.
1328 // Try to copy less by making a snapshot of only what is needed.
1329 sk_sp<SkImage> image = makeCheckedImageSnapshot(src->mSurface, srcRect);
1330 srcRect.offset(-srcRect.x(), -srcRect.y());
1331 getDrawCanvas()->drawImageRect(image, SkRect::Make(srcRect), destRect,
1332 makeSamplingOptions(rPosAry, mScaling, src->mScaling),
1333 &paint, SkCanvas::kFast_SrcRectConstraint);
1335 else
1337 // Do not use makeImageSnapshot(rect), as that one may make a needless data copy.
1338 getDrawCanvas()->drawImageRect(makeCheckedImageSnapshot(src->mSurface),
1339 SkRect::Make(srcRect), destRect,
1340 makeSamplingOptions(rPosAry, mScaling, src->mScaling),
1341 &paint, SkCanvas::kFast_SrcRectConstraint);
1345 bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect& rPosAry, const SalBitmap& rBitmap)
1347 if (checkInvalidSourceOrDestination(rPosAry))
1348 return false;
1350 assert(dynamic_cast<const SkiaSalBitmap*>(&rBitmap));
1351 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rBitmap);
1352 // This is used by VirtualDevice in the alpha mode for the "alpha" layer which
1353 // is actually one-minus-alpha (opacity). Therefore white=0xff=transparent,
1354 // black=0x00=opaque. So the result is transparent only if both the inputs
1355 // are transparent. Since for blending operations white=1.0 and black=0.0,
1356 // kMultiply should handle exactly that (transparent*transparent=transparent,
1357 // opaque*transparent=opaque). And guessing from the "floor" in TYPE_BLEND in opengl's
1358 // combinedTextureFragmentShader.glsl, the layer is not even alpha values but
1359 // simply yes-or-no mask.
1360 // See also blendAlphaBitmap().
1361 if (rSkiaBitmap.IsFullyOpaqueAsAlpha())
1363 // Optimization. If the bitmap means fully opaque, it's all zero's. In CPU
1364 // mode it should be faster to just copy instead of SkBlendMode::kMultiply.
1365 drawBitmap(rPosAry, rSkiaBitmap);
1367 else
1368 drawBitmap(rPosAry, rSkiaBitmap, SkBlendMode::kMultiply);
1369 return true;
1372 bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry,
1373 const SalBitmap& rSourceBitmap,
1374 const SalBitmap& rMaskBitmap,
1375 const SalBitmap& rAlphaBitmap)
1377 if (checkInvalidSourceOrDestination(rPosAry))
1378 return false;
1380 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1381 assert(dynamic_cast<const SkiaSalBitmap*>(&rMaskBitmap));
1382 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1383 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1384 const SkiaSalBitmap& rSkiaMaskBitmap = static_cast<const SkiaSalBitmap&>(rMaskBitmap);
1385 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1387 if (rSkiaMaskBitmap.IsFullyOpaqueAsAlpha())
1389 // Optimization. If the mask of the bitmap to be blended means it's actually opaque,
1390 // just draw the bitmap directly (that's what the math below will result in).
1391 drawBitmap(rPosAry, rSkiaSourceBitmap);
1392 return true;
1394 // This was originally implemented for the OpenGL drawing method and it is poorly documented.
1395 // The source and mask bitmaps are the usual data and alpha bitmaps, and 'alpha'
1396 // is the "alpha" layer of the VirtualDevice (the alpha in VirtualDevice is also stored
1397 // as a separate bitmap). Now if I understand it correctly these two alpha masks first need
1398 // to be combined into the actual alpha mask to be used. The formula for TYPE_BLEND
1399 // in opengl's combinedTextureFragmentShader.glsl is
1400 // "result_alpha = 1.0 - (1.0 - floor(alpha)) * mask".
1401 // See also blendBitmap().
1403 SkSamplingOptions samplingOptions = makeSamplingOptions(rPosAry, mScaling);
1404 // First do the "( 1 - alpha ) * mask"
1405 // (no idea how to do "floor", but hopefully not needed in practice).
1406 sk_sp<SkShader> shaderAlpha
1407 = SkShaders::Blend(SkBlendMode::kDstOut, rSkiaMaskBitmap.GetAlphaSkShader(samplingOptions),
1408 rSkiaAlphaBitmap.GetAlphaSkShader(samplingOptions));
1409 // And now draw the bitmap with "1 - x", where x is the "( 1 - alpha ) * mask".
1410 sk_sp<SkShader> shader = SkShaders::Blend(SkBlendMode::kSrcOut, shaderAlpha,
1411 rSkiaSourceBitmap.GetSkShader(samplingOptions));
1412 drawShader(rPosAry, shader);
1413 return true;
1416 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
1418 if (checkInvalidSourceOrDestination(rPosAry))
1419 return;
1421 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1422 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1424 drawBitmap(rPosAry, rSkiaSourceBitmap);
1427 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1428 const SalBitmap& rMaskBitmap)
1430 drawAlphaBitmap(rPosAry, rSalBitmap, rMaskBitmap);
1433 void SkiaSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1434 Color nMaskColor)
1436 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1437 const SkiaSalBitmap& skiaBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1438 drawShader(
1439 rPosAry,
1440 SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1441 SkShaders::Color(toSkColor(nMaskColor)),
1442 skiaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
1445 std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(tools::Long nX, tools::Long nY,
1446 tools::Long nWidth, tools::Long nHeight)
1448 SkiaZone zone;
1449 checkSurface();
1450 SAL_INFO("vcl.skia.trace",
1451 "getbitmap(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight));
1452 flushDrawing();
1453 // TODO makeImageSnapshot(rect) may copy the data, which may be a waste if this is used
1454 // e.g. for VirtualDevice's lame alpha blending, in which case the image will eventually end up
1455 // in blendAlphaBitmap(), where we could simply use the proper rect of the image.
1456 sk_sp<SkImage> image = makeCheckedImageSnapshot(
1457 mSurface, scaleRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), mScaling));
1458 std::shared_ptr<SkiaSalBitmap> bitmap = std::make_shared<SkiaSalBitmap>(image);
1459 // If the surface is scaled for HiDPI, the bitmap needs to be scaled down, otherwise
1460 // it would have incorrect size from the API point of view. The DirectImage::Yes handling
1461 // in mergeCacheBitmaps() should access the original unscaled bitmap data to avoid
1462 // pointless scaling back and forth.
1463 if (mScaling != 1)
1465 if (!isUnitTestRunning())
1466 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::BestQuality);
1467 else
1469 // Some tests require exact pixel values and would be confused by smooth-scaling.
1470 // And some draw something smooth and not smooth-scaling there would break the checks.
1471 if (isUnitTestRunning("BackendTest__testDrawHaflEllipseAAWithPolyLineB2D_")
1472 || isUnitTestRunning("BackendTest__testDrawRectAAWithLine_"))
1474 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::BestQuality);
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);
1503 SkPath aPath;
1504 aPath.incReserve(rPoly.count());
1505 addPolygonToPath(rPoly, aPath);
1506 aPath.setFillType(SkPathFillType::kEvenOdd);
1507 addUpdateRegion(aPath.getBounds());
1508 SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
1509 SkPaint aPaint;
1510 setBlendModeDifference(&aPaint);
1511 // TrackFrame just inverts a dashed path around the polygon
1512 if (eFlags == SalInvert::TrackFrame)
1514 // TrackFrame is not supposed to paint outside of the polygon (usually rectangle),
1515 // but wider stroke width usually results in that, so ensure the requirement
1516 // by clipping.
1517 getDrawCanvas()->clipRect(aPath.getBounds(), SkClipOp::kIntersect, false);
1518 aPaint.setStrokeWidth(2);
1519 constexpr float intervals[] = { 4.0f, 4.0f };
1520 aPaint.setStyle(SkPaint::kStroke_Style);
1521 aPaint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
1522 aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
1524 else
1526 aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
1527 aPaint.setStyle(SkPaint::kFill_Style);
1529 // N50 inverts in checker pattern
1530 if (eFlags == SalInvert::N50)
1532 // This creates 2x2 checker pattern bitmap
1533 // TODO Use createSkSurface() and cache the image
1534 SkBitmap aBitmap;
1535 aBitmap.allocN32Pixels(2, 2);
1536 const SkPMColor white = SkPreMultiplyARGB(0xFF, 0xFF, 0xFF, 0xFF);
1537 const SkPMColor black = SkPreMultiplyARGB(0xFF, 0x00, 0x00, 0x00);
1538 SkPMColor* scanline;
1539 scanline = aBitmap.getAddr32(0, 0);
1540 *scanline++ = white;
1541 *scanline++ = black;
1542 scanline = aBitmap.getAddr32(0, 1);
1543 *scanline++ = black;
1544 *scanline++ = white;
1545 aBitmap.setImmutable();
1546 // The bitmap is repeated in both directions the checker pattern is as big
1547 // as the polygon (usually rectangle)
1548 aPaint.setShader(
1549 aBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()));
1552 getDrawCanvas()->drawPath(aPath, aPaint);
1553 postDraw();
1556 void SkiaSalGraphicsImpl::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
1557 tools::Long nHeight, SalInvert eFlags)
1559 basegfx::B2DRectangle aRectangle(nX, nY, nX + nWidth, nY + nHeight);
1560 auto aRect = basegfx::utils::createPolygonFromRect(aRectangle);
1561 invert(aRect, eFlags);
1564 void SkiaSalGraphicsImpl::invert(sal_uInt32 nPoints, const Point* pPointArray, SalInvert eFlags)
1566 basegfx::B2DPolygon aPolygon;
1567 aPolygon.append(basegfx::B2DPoint(pPointArray[0].getX(), pPointArray[0].getY()), nPoints);
1568 for (sal_uInt32 i = 1; i < nPoints; ++i)
1570 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPointArray[i].getX(), pPointArray[i].getY()));
1572 aPolygon.setClosed(true);
1574 invert(aPolygon, eFlags);
1577 bool SkiaSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*,
1578 sal_uInt32)
1580 return false;
1583 // Create SkImage from a bitmap and possibly an alpha mask (the usual VCL one-minus-alpha),
1584 // with the given target size. Result will be possibly cached, unless disabled.
1585 // Especially in raster mode scaling and alpha blending may be expensive if done repeatedly.
1586 sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitmap,
1587 const SkiaSalBitmap* alphaBitmap,
1588 const Size& targetSize)
1590 if (alphaBitmap)
1591 assert(bitmap.GetSize() == alphaBitmap->GetSize());
1593 if (targetSize.IsEmpty())
1594 return {};
1595 if (alphaBitmap && alphaBitmap->IsFullyOpaqueAsAlpha())
1596 alphaBitmap = nullptr; // the alpha can be ignored
1597 if (bitmap.PreferSkShader() && (!alphaBitmap || alphaBitmap->PreferSkShader()))
1598 return {};
1600 // If the bitmap has SkImage that matches the required size, try to use it, even
1601 // if it doesn't match bitmap.GetSize(). This can happen with delayed scaling.
1602 // This will catch cases such as some code pre-scaling the bitmap, which would make GetSkImage()
1603 // scale, changing GetImageKey() in the process so we'd have to re-cache, and then we'd need
1604 // to scale again in this function.
1605 bool bitmapReady = false;
1606 bool alphaBitmapReady = false;
1607 if (const sk_sp<SkImage>& image = bitmap.GetSkImage(DirectImage::Yes))
1609 assert(!bitmap.PreferSkShader());
1610 if (imageSize(image) == targetSize)
1611 bitmapReady = true;
1613 // If the image usable and there's no alpha, then it matches exactly what's wanted.
1614 if (bitmapReady && !alphaBitmap)
1615 return bitmap.GetSkImage(DirectImage::Yes);
1616 if (alphaBitmap)
1618 if (!alphaBitmap->GetAlphaSkImage(DirectImage::Yes)
1619 && alphaBitmap->GetSkImage(DirectImage::Yes)
1620 && imageSize(alphaBitmap->GetSkImage(DirectImage::Yes)) == targetSize)
1622 // There's a usable non-alpha image, try to convert it to alpha.
1623 assert(!alphaBitmap->PreferSkShader());
1624 const_cast<SkiaSalBitmap*>(alphaBitmap)->TryDirectConvertToAlphaNoScaling();
1626 if (const sk_sp<SkImage>& image = alphaBitmap->GetAlphaSkImage(DirectImage::Yes))
1628 assert(!alphaBitmap->PreferSkShader());
1629 if (imageSize(image) == targetSize)
1630 alphaBitmapReady = true;
1634 if (bitmapReady && (!alphaBitmap || alphaBitmapReady))
1636 // Try to find a cached image based on the already existing images.
1637 OString key = makeCachedImageKey(bitmap, alphaBitmap, targetSize, DirectImage::Yes,
1638 DirectImage::Yes);
1639 if (sk_sp<SkImage> image = findCachedImage(key))
1641 assert(imageSize(image) == targetSize);
1642 return image;
1646 // Probably not much point in caching of just doing a copy.
1647 if (alphaBitmap == nullptr && targetSize == bitmap.GetSize())
1648 return {};
1649 // Image too small to be worth caching if not scaling.
1650 if (targetSize == bitmap.GetSize() && targetSize.Width() < 100 && targetSize.Height() < 100)
1651 return {};
1652 // GPU-accelerated drawing with SkShader should be fast enough to not need caching.
1653 if (isGPU())
1655 // tdf#140925: But if this is such an extensive downscaling that caching the result
1656 // would noticeably reduce amount of data processed by the GPU on repeated usage, do it.
1657 int reduceRatio = bitmap.GetSize().Width() * bitmap.GetSize().Height() / targetSize.Width()
1658 / targetSize.Height();
1659 if (reduceRatio < 10)
1660 return {};
1662 // In some cases (tdf#134237) the target size may be very large. In that case it's
1663 // better to rely on Skia to clip and draw only the necessary, rather than prepare
1664 // a very large image only to not use most of it.
1665 const Size drawAreaSize = mClipRegion.GetBoundRect().GetSize() * mScaling;
1666 if (targetSize.Width() > drawAreaSize.Width() || targetSize.Height() > drawAreaSize.Height())
1668 // This is a bit tricky. The condition above just checks that at least a part of the resulting
1669 // image will not be used (it's larger then our drawing area). But this may often happen
1670 // when just scrolling a document with a large image, where the caching may very well be worth it.
1671 // Since the problem is mainly the cost of upscaling and then the size of the resulting bitmap,
1672 // compute a ratio of how much this is going to be scaled up, how much this is larger than
1673 // the drawing area, and then refuse to cache if it's too much.
1674 const double upscaleRatio
1675 = std::max(1.0, 1.0 * targetSize.Width() / bitmap.GetSize().Width()
1676 * targetSize.Height() / bitmap.GetSize().Height());
1677 const double oversizeRatio = 1.0 * targetSize.Width() / drawAreaSize.Width()
1678 * targetSize.Height() / drawAreaSize.Height();
1679 const double ratio = upscaleRatio * oversizeRatio;
1680 if (ratio > 4)
1682 SAL_INFO("vcl.skia.trace", "mergecachebitmaps("
1683 << this << "): not caching, ratio:" << ratio << ", "
1684 << bitmap.GetSize() << "->" << targetSize << " in "
1685 << drawAreaSize);
1686 return {};
1689 // Do not cache the result if it would take most of the cache and thus get evicted soon.
1690 if (targetSize.Width() * targetSize.Height() * 4 > maxImageCacheSize() * 0.7)
1691 return {};
1693 // Use ready direct image if they are both available, now even the size doesn't matter
1694 // (we'll scale as necessary and it's better to scale from the original). Require only
1695 // that they are the same size, or that one prefers a shader or doesn't exist
1696 // (i.e. avoid two images of different size).
1697 bitmapReady = bitmap.GetSkImage(DirectImage::Yes) != nullptr;
1698 alphaBitmapReady = alphaBitmap && alphaBitmap->GetAlphaSkImage(DirectImage::Yes) != nullptr;
1699 if (bitmapReady && alphaBitmap && !alphaBitmapReady && !alphaBitmap->PreferSkShader())
1700 bitmapReady = false;
1701 if (alphaBitmapReady && !bitmapReady && bitmap.PreferSkShader())
1702 alphaBitmapReady = false;
1704 DirectImage bitmapType = bitmapReady ? DirectImage::Yes : DirectImage::No;
1705 DirectImage alphaBitmapType = alphaBitmapReady ? DirectImage::Yes : DirectImage::No;
1707 // Try to find a cached result, this time after possible delayed scaling.
1708 OString key = makeCachedImageKey(bitmap, alphaBitmap, targetSize, bitmapType, alphaBitmapType);
1709 if (sk_sp<SkImage> image = findCachedImage(key))
1711 assert(imageSize(image) == targetSize);
1712 return image;
1715 Size sourceSize;
1716 if (bitmapReady)
1717 sourceSize = imageSize(bitmap.GetSkImage(DirectImage::Yes));
1718 else if (alphaBitmapReady)
1719 sourceSize = imageSize(alphaBitmap->GetAlphaSkImage(DirectImage::Yes));
1720 else
1721 sourceSize = bitmap.GetSize();
1723 // Generate a new result and cache it.
1724 sk_sp<SkSurface> tmpSurface
1725 = createSkSurface(targetSize, alphaBitmap ? kPremul_SkAlphaType : bitmap.alphaType());
1726 if (!tmpSurface)
1727 return nullptr;
1728 SkCanvas* canvas = tmpSurface->getCanvas();
1729 SkAutoCanvasRestore autoRestore(canvas, true);
1730 SkPaint paint;
1731 SkSamplingOptions samplingOptions;
1732 if (targetSize != sourceSize)
1734 SkMatrix matrix;
1735 matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / sourceSize.Width());
1736 matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / sourceSize.Height());
1737 canvas->concat(matrix);
1738 if (!isUnitTestRunning()) // unittests want exact pixel values
1739 samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix, 1);
1741 if (alphaBitmap != nullptr)
1743 canvas->clear(SK_ColorTRANSPARENT);
1744 paint.setShader(
1745 SkShaders::Blend(SkBlendMode::kDstOut, bitmap.GetSkShader(samplingOptions, bitmapType),
1746 alphaBitmap->GetAlphaSkShader(samplingOptions, alphaBitmapType)));
1747 canvas->drawPaint(paint);
1749 else if (bitmap.PreferSkShader())
1751 paint.setShader(bitmap.GetSkShader(samplingOptions, bitmapType));
1752 canvas->drawPaint(paint);
1754 else
1755 canvas->drawImage(bitmap.GetSkImage(bitmapType), 0, 0, samplingOptions, &paint);
1756 if (isGPU())
1757 SAL_INFO("vcl.skia.trace", "mergecachebitmaps(" << this << "): caching GPU downscaling:"
1758 << bitmap.GetSize() << "->" << targetSize);
1759 sk_sp<SkImage> image = makeCheckedImageSnapshot(tmpSurface);
1760 addCachedImage(key, image);
1761 return image;
1764 OString SkiaSalGraphicsImpl::makeCachedImageKey(const SkiaSalBitmap& bitmap,
1765 const SkiaSalBitmap* alphaBitmap,
1766 const Size& targetSize, DirectImage bitmapType,
1767 DirectImage alphaBitmapType)
1769 OString key = OString::number(targetSize.Width()) + "x" + OString::number(targetSize.Height())
1770 + "_" + bitmap.GetImageKey(bitmapType);
1771 if (alphaBitmap)
1772 key += "_" + alphaBitmap->GetAlphaImageKey(alphaBitmapType);
1773 return key;
1776 bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
1777 const SalBitmap& rAlphaBitmap)
1779 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1780 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1781 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1782 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1783 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1784 // alpha blending or scaling.
1785 SalTwoRect imagePosAry(rPosAry);
1786 Size imageSize = rSourceBitmap.GetSize();
1787 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1788 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1789 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1790 && rPosAry.mnSrcWidth == rSourceBitmap.GetSize().Width()
1791 && rPosAry.mnSrcHeight == rSourceBitmap.GetSize().Height())
1793 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1794 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1795 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1797 sk_sp<SkImage> image
1798 = mergeCacheBitmaps(rSkiaSourceBitmap, &rSkiaAlphaBitmap, imageSize * mScaling);
1799 if (image)
1800 drawImage(imagePosAry, image, mScaling);
1801 else if (rSkiaAlphaBitmap.IsFullyOpaqueAsAlpha()
1802 && !rSkiaSourceBitmap.PreferSkShader()) // alpha can be ignored
1803 drawBitmap(rPosAry, rSkiaSourceBitmap);
1804 else
1805 drawShader(rPosAry,
1806 SkShaders::Blend(
1807 SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1808 rSkiaSourceBitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)),
1809 rSkiaAlphaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
1810 return true;
1813 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBitmap& bitmap,
1814 SkBlendMode blendMode)
1816 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1817 // scaling.
1818 SalTwoRect imagePosAry(rPosAry);
1819 Size imageSize = bitmap.GetSize();
1820 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1821 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1822 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1823 && rPosAry.mnSrcWidth == bitmap.GetSize().Width()
1824 && rPosAry.mnSrcHeight == bitmap.GetSize().Height())
1826 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1827 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1828 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1830 sk_sp<SkImage> image = mergeCacheBitmaps(bitmap, nullptr, imageSize * mScaling);
1831 if (image)
1832 drawImage(imagePosAry, image, mScaling, blendMode);
1833 else if (bitmap.PreferSkShader())
1834 drawShader(rPosAry, bitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)), blendMode);
1835 else
1836 drawImage(rPosAry, bitmap.GetSkImage(), 1, blendMode);
1839 void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage,
1840 int srcScaling, SkBlendMode eBlendMode)
1842 SkRect aSourceRect
1843 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1844 if (srcScaling != 1)
1845 aSourceRect = scaleRect(aSourceRect, srcScaling);
1846 SkRect aDestinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY,
1847 rPosAry.mnDestWidth, rPosAry.mnDestHeight);
1849 SkPaint aPaint;
1850 aPaint.setBlendMode(eBlendMode);
1852 preDraw();
1853 SAL_INFO("vcl.skia.trace",
1854 "drawimage(" << this << "): " << rPosAry << ":" << SkBlendMode_Name(eBlendMode));
1855 addUpdateRegion(aDestinationRect);
1856 getDrawCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect,
1857 makeSamplingOptions(rPosAry, mScaling, srcScaling), &aPaint,
1858 SkCanvas::kFast_SrcRectConstraint);
1859 ++mPendingOperationsToFlush; // tdf#136369
1860 postDraw();
1863 // SkShader can be used to merge multiple bitmaps with appropriate blend modes (e.g. when
1864 // merging a bitmap with its alpha mask).
1865 void SkiaSalGraphicsImpl::drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader,
1866 SkBlendMode blendMode)
1868 preDraw();
1869 SAL_INFO("vcl.skia.trace", "drawshader(" << this << "): " << rPosAry);
1870 SkRect destinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1871 rPosAry.mnDestHeight);
1872 addUpdateRegion(destinationRect);
1873 SkPaint paint;
1874 paint.setBlendMode(blendMode);
1875 paint.setShader(shader);
1876 SkCanvas* canvas = getDrawCanvas();
1877 // Scaling needs to be done explicitly using a matrix.
1878 SkAutoCanvasRestore autoRestore(canvas, true);
1879 SkMatrix matrix = SkMatrix::Translate(rPosAry.mnDestX, rPosAry.mnDestY)
1880 * SkMatrix::Scale(1.0 * rPosAry.mnDestWidth / rPosAry.mnSrcWidth,
1881 1.0 * rPosAry.mnDestHeight / rPosAry.mnSrcHeight)
1882 * SkMatrix::Translate(-rPosAry.mnSrcX, -rPosAry.mnSrcY);
1883 #ifndef NDEBUG
1884 // Handle floating point imprecisions, round p1 to 2 decimal places.
1885 auto compareRounded = [](const SkPoint& p1, const SkPoint& p2) {
1886 return rtl::math::round(p1.x(), 2) == p2.x() && rtl::math::round(p1.y(), 2) == p2.y();
1888 #endif
1889 assert(compareRounded(matrix.mapXY(rPosAry.mnSrcX, rPosAry.mnSrcY),
1890 SkPoint::Make(rPosAry.mnDestX, rPosAry.mnDestY)));
1891 assert(compareRounded(
1892 matrix.mapXY(rPosAry.mnSrcX + rPosAry.mnSrcWidth, rPosAry.mnSrcY + rPosAry.mnSrcHeight),
1893 SkPoint::Make(rPosAry.mnDestX + rPosAry.mnDestWidth,
1894 rPosAry.mnDestY + rPosAry.mnDestHeight)));
1895 canvas->concat(matrix);
1896 SkRect sourceRect
1897 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1898 canvas->drawRect(sourceRect, paint);
1899 postDraw();
1902 bool SkiaSalGraphicsImpl::hasFastDrawTransformedBitmap() const
1904 // Return true even in raster mode, even that way Skia is faster than e.g. GraphicObject
1905 // trying to handle stuff manually.
1906 return true;
1909 // Whether applying matrix needs image smoothing for the transformation.
1910 static bool matrixNeedsHighQuality(const SkMatrix& matrix)
1912 if (matrix.isIdentity())
1913 return false;
1914 if (matrix.isScaleTranslate())
1916 if (abs(matrix.getScaleX()) == 1 && abs(matrix.getScaleY()) == 1)
1917 return false; // Only at most flipping and keeping the size.
1918 return true;
1920 assert(!matrix.hasPerspective()); // we do not use this
1921 if (matrix.getScaleX() == 0 && matrix.getScaleY() == 0)
1923 // Rotating 90 or 270 degrees while keeping the size.
1924 if ((matrix.getSkewX() == 1 && matrix.getSkewY() == -1)
1925 || (matrix.getSkewX() == -1 && matrix.getSkewY() == 1))
1926 return false;
1928 return true;
1931 namespace SkiaTests
1933 bool matrixNeedsHighQuality(const SkMatrix& matrix) { return ::matrixNeedsHighQuality(matrix); }
1936 bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
1937 const basegfx::B2DPoint& rX,
1938 const basegfx::B2DPoint& rY,
1939 const SalBitmap& rSourceBitmap,
1940 const SalBitmap* pAlphaBitmap, double fAlpha)
1942 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1943 assert(!pAlphaBitmap || dynamic_cast<const SkiaSalBitmap*>(pAlphaBitmap));
1945 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1946 const SkiaSalBitmap* pSkiaAlphaBitmap = static_cast<const SkiaSalBitmap*>(pAlphaBitmap);
1948 if (pSkiaAlphaBitmap && pSkiaAlphaBitmap->IsFullyOpaqueAsAlpha())
1949 pSkiaAlphaBitmap = nullptr; // the alpha can be ignored
1951 // Setup the image transformation,
1952 // using the rNull, rX, rY points as destinations for the (0,0), (Width,0), (0,Height) source points.
1953 const basegfx::B2DVector aXRel = rX - rNull;
1954 const basegfx::B2DVector aYRel = rY - rNull;
1956 preDraw();
1957 SAL_INFO("vcl.skia.trace", "drawtransformedbitmap(" << this << "): " << rSourceBitmap.GetSize()
1958 << " " << rNull << ":" << rX << ":" << rY);
1960 addUpdateRegion(SkRect::MakeWH(GetWidth(), GetHeight())); // can't tell, use whole area
1961 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1962 // alpha blending or scaling.
1963 // The extra fAlpha blending is not cached, with the assumption that it usually gradually changes
1964 // for each invocation.
1965 // Pass size * mScaling to mergeCacheBitmaps() so that it prepares the size that will be needed
1966 // after the mScaling-scaling matrix, but otherwise calculate everything else using the VCL coordinates.
1967 Size imageSize(round(aXRel.getLength()), round(aYRel.getLength()));
1968 sk_sp<SkImage> imageToDraw
1969 = mergeCacheBitmaps(rSkiaBitmap, pSkiaAlphaBitmap, imageSize * mScaling);
1970 if (imageToDraw)
1972 SkMatrix matrix;
1973 // Round sizes for scaling, so that sub-pixel differences don't
1974 // trigger unnecessary scaling. Image has already been scaled
1975 // by mergeCacheBitmaps() and we shouldn't scale here again
1976 // unless the drawing is also skewed.
1977 matrix.set(SkMatrix::kMScaleX, round(aXRel.getX()) / imageSize.Width());
1978 matrix.set(SkMatrix::kMScaleY, round(aYRel.getY()) / imageSize.Height());
1979 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / imageSize.Width());
1980 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / imageSize.Height());
1981 matrix.set(SkMatrix::kMTransX, rNull.getX());
1982 matrix.set(SkMatrix::kMTransY, rNull.getY());
1983 SkCanvas* canvas = getDrawCanvas();
1984 SkAutoCanvasRestore autoRestore(canvas, true);
1985 canvas->concat(matrix);
1986 SkSamplingOptions samplingOptions;
1987 // If the matrix changes geometry, we need to smooth-scale. If there's mScaling,
1988 // that's already been handled by mergeCacheBitmaps().
1989 if (matrixNeedsHighQuality(matrix))
1990 samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix, 1);
1991 if (fAlpha == 1.0)
1993 // Specify sizes to scale the image size back if needed (because of mScaling).
1994 SkRect dstRect = SkRect::MakeWH(imageSize.Width(), imageSize.Height());
1995 SkRect srcRect = SkRect::MakeWH(imageToDraw->width(), imageToDraw->height());
1996 canvas->drawImageRect(imageToDraw, srcRect, dstRect, samplingOptions, nullptr,
1997 SkCanvas::kFast_SrcRectConstraint);
1999 else
2001 SkPaint paint;
2002 // Scale the image size back if needed.
2003 SkMatrix scale = SkMatrix::Scale(1.0 / mScaling, 1.0 / mScaling);
2004 paint.setShader(SkShaders::Blend(
2005 SkBlendMode::kDstIn, imageToDraw->makeShader(samplingOptions, &scale),
2006 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
2007 canvas->drawRect(SkRect::MakeWH(imageSize.Width(), imageSize.Height()), paint);
2010 else
2012 SkMatrix matrix;
2013 const Size aSize = rSourceBitmap.GetSize();
2014 matrix.set(SkMatrix::kMScaleX, aXRel.getX() / aSize.Width());
2015 matrix.set(SkMatrix::kMScaleY, aYRel.getY() / aSize.Height());
2016 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / aSize.Width());
2017 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / aSize.Height());
2018 matrix.set(SkMatrix::kMTransX, rNull.getX());
2019 matrix.set(SkMatrix::kMTransY, rNull.getY());
2020 SkCanvas* canvas = getDrawCanvas();
2021 SkAutoCanvasRestore autoRestore(canvas, true);
2022 canvas->concat(matrix);
2023 SkSamplingOptions samplingOptions;
2024 if (matrixNeedsHighQuality(matrix) || (mScaling != 1 && !isUnitTestRunning()))
2025 samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix, mScaling);
2026 if (pSkiaAlphaBitmap)
2028 SkPaint paint;
2029 paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
2030 rSkiaBitmap.GetSkShader(samplingOptions),
2031 pSkiaAlphaBitmap->GetAlphaSkShader(samplingOptions)));
2032 if (fAlpha != 1.0)
2033 paint.setShader(
2034 SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
2035 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
2036 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
2038 else if (rSkiaBitmap.PreferSkShader() || fAlpha != 1.0)
2040 SkPaint paint;
2041 paint.setShader(rSkiaBitmap.GetSkShader(samplingOptions));
2042 if (fAlpha != 1.0)
2043 paint.setShader(
2044 SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
2045 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
2046 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
2048 else
2050 canvas->drawImage(rSkiaBitmap.GetSkImage(), 0, 0, samplingOptions);
2053 postDraw();
2054 return true;
2057 bool SkiaSalGraphicsImpl::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
2058 tools::Long nHeight, sal_uInt8 nTransparency)
2060 privateDrawAlphaRect(nX, nY, nWidth, nHeight, nTransparency / 100.0);
2061 return true;
2064 bool SkiaSalGraphicsImpl::drawGradient(const tools::PolyPolygon& rPolyPolygon,
2065 const Gradient& rGradient)
2067 if (rGradient.GetStyle() != GradientStyle::Linear
2068 && rGradient.GetStyle() != GradientStyle::Axial
2069 && rGradient.GetStyle() != GradientStyle::Radial)
2070 return false; // unsupported
2071 if (rGradient.GetSteps() != 0)
2072 return false; // We can't tell Skia how many colors to use in the gradient.
2073 preDraw();
2074 SAL_INFO("vcl.skia.trace", "drawgradient(" << this << "): " << rPolyPolygon.getB2DPolyPolygon()
2075 << ":" << static_cast<int>(rGradient.GetStyle()));
2076 tools::Rectangle boundRect(rPolyPolygon.GetBoundRect());
2077 if (boundRect.IsEmpty())
2078 return true;
2079 SkPath path;
2080 if (rPolyPolygon.IsRect())
2082 // Rect->Polygon conversion loses the right and bottom edge, fix that.
2083 path.addRect(SkRect::MakeXYWH(boundRect.getX(), boundRect.getY(), boundRect.GetWidth(),
2084 boundRect.GetHeight()));
2085 boundRect.AdjustRight(1);
2086 boundRect.AdjustBottom(1);
2088 else
2089 addPolyPolygonToPath(rPolyPolygon.getB2DPolyPolygon(), path);
2090 path.setFillType(SkPathFillType::kEvenOdd);
2091 addUpdateRegion(path.getBounds());
2093 Gradient aGradient(rGradient);
2094 tools::Rectangle aBoundRect;
2095 Point aCenter;
2096 aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10);
2097 aGradient.GetBoundRect(boundRect, aBoundRect, aCenter);
2099 SkColor startColor
2100 = toSkColorWithIntensity(rGradient.GetStartColor(), rGradient.GetStartIntensity());
2101 SkColor endColor = toSkColorWithIntensity(rGradient.GetEndColor(), rGradient.GetEndIntensity());
2103 sk_sp<SkShader> shader;
2104 if (rGradient.GetStyle() == GradientStyle::Linear)
2106 tools::Polygon aPoly(aBoundRect);
2107 aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
2108 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
2109 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
2110 SkColor colors[2] = { startColor, endColor };
2111 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
2112 shader = SkGradientShader::MakeLinear(points, colors, pos, 2, SkTileMode::kClamp);
2114 else if (rGradient.GetStyle() == GradientStyle::Axial)
2116 tools::Polygon aPoly(aBoundRect);
2117 aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
2118 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
2119 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
2120 SkColor colors[3] = { endColor, startColor, endColor };
2121 SkScalar border = SkDoubleToScalar(aGradient.GetBorder() / 100.0);
2122 SkScalar pos[3]
2123 = { std::min<SkScalar>(border, 0.5), 0.5, std::max<SkScalar>(1 - border, 0.5) };
2124 shader = SkGradientShader::MakeLinear(points, colors, pos, 3, SkTileMode::kClamp);
2126 else
2128 // Move the center by (-1,-1) (the default VCL algorithm is a bit off-center that way,
2129 // Skia is the opposite way).
2130 SkPoint center = SkPoint::Make(toSkX(aCenter.X()) - 1, toSkY(aCenter.Y()) - 1);
2131 SkScalar radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
2132 SkColor colors[2] = { endColor, startColor };
2133 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
2134 shader = SkGradientShader::MakeRadial(center, radius, colors, pos, 2, SkTileMode::kClamp);
2137 SkPaint paint;
2138 paint.setAntiAlias(mParent.getAntiAlias());
2139 paint.setShader(shader);
2140 getDrawCanvas()->drawPath(path, paint);
2141 postDraw();
2142 return true;
2145 bool SkiaSalGraphicsImpl::implDrawGradient(const basegfx::B2DPolyPolygon& rPolyPolygon,
2146 const SalGradient& rGradient)
2148 preDraw();
2149 SAL_INFO("vcl.skia.trace",
2150 "impldrawgradient(" << this << "): " << rPolyPolygon << ":" << rGradient.maPoint1
2151 << "->" << rGradient.maPoint2 << ":" << rGradient.maStops.size());
2153 SkPath path;
2154 addPolyPolygonToPath(rPolyPolygon, path);
2155 path.setFillType(SkPathFillType::kEvenOdd);
2156 addUpdateRegion(path.getBounds());
2158 SkPoint points[2]
2159 = { SkPoint::Make(toSkX(rGradient.maPoint1.getX()), toSkY(rGradient.maPoint1.getY())),
2160 SkPoint::Make(toSkX(rGradient.maPoint2.getX()), toSkY(rGradient.maPoint2.getY())) };
2161 std::vector<SkColor> colors;
2162 std::vector<SkScalar> pos;
2163 for (const SalGradientStop& stop : rGradient.maStops)
2165 colors.emplace_back(toSkColor(stop.maColor));
2166 pos.emplace_back(stop.mfOffset);
2168 sk_sp<SkShader> shader = SkGradientShader::MakeLinear(points, colors.data(), pos.data(),
2169 colors.size(), SkTileMode::kDecal);
2170 SkPaint paint;
2171 paint.setAntiAlias(mParent.getAntiAlias());
2172 paint.setShader(shader);
2173 getDrawCanvas()->drawPath(path, paint);
2174 postDraw();
2175 return true;
2178 static double toRadian(Degree10 degree10th) { return toRadians(3600_deg10 - degree10th); }
2179 static double toCos(Degree10 degree10th) { return SkScalarCos(toRadian(degree10th)); }
2180 static double toSin(Degree10 degree10th) { return SkScalarSin(toRadian(degree10th)); }
2182 void SkiaSalGraphicsImpl::drawGenericLayout(const GenericSalLayout& layout, Color textColor,
2183 const SkFont& font, const SkFont& verticalFont)
2185 SkiaZone zone;
2186 std::vector<SkGlyphID> glyphIds;
2187 std::vector<SkRSXform> glyphForms;
2188 std::vector<bool> verticals;
2189 glyphIds.reserve(256);
2190 glyphForms.reserve(256);
2191 verticals.reserve(256);
2192 Point aPos;
2193 const GlyphItem* pGlyph;
2194 int nStart = 0;
2195 while (layout.GetNextGlyph(&pGlyph, aPos, nStart))
2197 glyphIds.push_back(pGlyph->glyphId());
2198 Degree10 angle = layout.GetOrientation();
2199 if (pGlyph->IsVertical())
2200 angle += 900_deg10;
2201 SkRSXform form = SkRSXform::Make(toCos(angle), toSin(angle), aPos.X(), aPos.Y());
2202 glyphForms.emplace_back(std::move(form));
2203 verticals.emplace_back(pGlyph->IsVertical());
2205 if (glyphIds.empty())
2206 return;
2208 preDraw();
2209 auto getBoundRect = [&layout]() {
2210 tools::Rectangle rect;
2211 layout.GetBoundRect(rect);
2212 return rect;
2214 SAL_INFO("vcl.skia.trace", "drawtextblob(" << this << "): " << getBoundRect() << ", "
2215 << glyphIds.size() << " glyphs, " << textColor);
2217 // Vertical glyphs need a different font, so split drawing into runs that each
2218 // draw only consecutive horizontal or vertical glyphs.
2219 std::vector<bool>::const_iterator pos = verticals.cbegin();
2220 std::vector<bool>::const_iterator end = verticals.cend();
2221 while (pos != end)
2223 bool verticalRun = *pos;
2224 std::vector<bool>::const_iterator rangeEnd = std::find(pos + 1, end, !verticalRun);
2225 size_t index = pos - verticals.cbegin();
2226 size_t count = rangeEnd - pos;
2227 sk_sp<SkTextBlob> textBlob = SkTextBlob::MakeFromRSXform(
2228 glyphIds.data() + index, count * sizeof(SkGlyphID), glyphForms.data() + index,
2229 verticalRun ? verticalFont : font, SkTextEncoding::kGlyphID);
2230 addUpdateRegion(textBlob->bounds());
2231 SkPaint paint;
2232 paint.setColor(toSkColor(textColor));
2233 getDrawCanvas()->drawTextBlob(textBlob, 0, 0, paint);
2234 pos = rangeEnd;
2236 postDraw();
2239 bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
2241 switch (eType)
2243 case OutDevSupportType::B2DDraw:
2244 case OutDevSupportType::TransparentRect:
2245 return true;
2246 default:
2247 return false;
2251 static int getScaling()
2253 // It makes sense to support the debugging flag on all platforms
2254 // for unittests purpose, even if the actual windows cannot do it.
2255 if (const char* env = getenv("SAL_FORCE_HIDPI_SCALING"))
2256 return atoi(env);
2257 return 1;
2260 int SkiaSalGraphicsImpl::getWindowScaling() const
2262 static const int scaling = getScaling();
2263 return scaling;
2266 void SkiaSalGraphicsImpl::dump(const char* file) const
2268 assert(mSurface.get());
2269 SkiaHelper::dump(mSurface, file);
2272 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */