don't bother caching bitmaps that prefer shaders
[LibreOffice.git] / vcl / skia / gdiimpl.cxx
blobb97d2f28c42885da4d689d1d14bdd6ef305fb7b5
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,
59 bool* hasOnlyOrthogonal = nullptr)
61 const sal_uInt32 nPointCount(rPolygon.count());
63 if (nPointCount <= 1)
64 return;
66 const bool bClosePath(rPolygon.isClosed());
67 const bool bHasCurves(rPolygon.areControlPointsUsed());
69 bool bFirst = true;
71 sal_uInt32 nCurrentIndex = 0;
72 sal_uInt32 nPreviousIndex = nPointCount - 1;
74 basegfx::B2DPoint aCurrentPoint;
75 basegfx::B2DPoint aPreviousPoint;
77 for (sal_uInt32 nIndex = 0; nIndex <= nPointCount; nIndex++)
79 if (nIndex == nPointCount && !bClosePath)
80 continue;
82 // Make sure we loop the last point to first point
83 nCurrentIndex = nIndex % nPointCount;
84 aCurrentPoint = rPolygon.getB2DPoint(nCurrentIndex);
86 if (bFirst)
88 rPath.moveTo(aCurrentPoint.getX(), aCurrentPoint.getY());
89 bFirst = false;
91 else if (!bHasCurves)
93 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY());
94 // If asked for, check whether the polygon has a line that is not
95 // strictly horizontal or vertical.
96 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
97 && aCurrentPoint.getY() != aPreviousPoint.getY())
98 *hasOnlyOrthogonal = false;
100 else
102 basegfx::B2DPoint aPreviousControlPoint = rPolygon.getNextControlPoint(nPreviousIndex);
103 basegfx::B2DPoint aCurrentControlPoint = rPolygon.getPrevControlPoint(nCurrentIndex);
105 if (aPreviousControlPoint.equal(aPreviousPoint)
106 && aCurrentControlPoint.equal(aCurrentPoint))
108 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY()); // a straight line
109 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
110 && aCurrentPoint.getY() != aPreviousPoint.getY())
111 *hasOnlyOrthogonal = false;
113 else
115 if (aPreviousControlPoint.equal(aPreviousPoint))
117 aPreviousControlPoint
118 = aPreviousPoint + ((aPreviousControlPoint - aCurrentPoint) * 0.0005);
120 if (aCurrentControlPoint.equal(aCurrentPoint))
122 aCurrentControlPoint
123 = aCurrentPoint + ((aCurrentControlPoint - aPreviousPoint) * 0.0005);
125 rPath.cubicTo(aPreviousControlPoint.getX(), aPreviousControlPoint.getY(),
126 aCurrentControlPoint.getX(), aCurrentControlPoint.getY(),
127 aCurrentPoint.getX(), aCurrentPoint.getY());
128 if (hasOnlyOrthogonal != nullptr)
129 *hasOnlyOrthogonal = false;
132 aPreviousPoint = aCurrentPoint;
133 nPreviousIndex = nCurrentIndex;
135 if (bClosePath)
137 rPath.close();
141 void addPolyPolygonToPath(const basegfx::B2DPolyPolygon& rPolyPolygon, SkPath& rPath,
142 bool* hasOnlyOrthogonal = nullptr)
144 const sal_uInt32 nPolygonCount(rPolyPolygon.count());
146 if (nPolygonCount == 0)
147 return;
149 sal_uInt32 nPointCount = 0;
150 for (const auto& rPolygon : rPolyPolygon)
151 nPointCount += rPolygon.count() * 3; // because cubicTo is 3 elements
152 rPath.incReserve(nPointCount);
154 for (const auto& rPolygon : rPolyPolygon)
156 addPolygonToPath(rPolygon, rPath, hasOnlyOrthogonal);
160 // Check if the given polygon contains a straight line. If not, it consists
161 // solely of curves.
162 bool polygonContainsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
164 if (!rPolyPolygon.areControlPointsUsed())
165 return true; // no curves at all
166 for (const auto& rPolygon : rPolyPolygon)
168 const sal_uInt32 nPointCount(rPolygon.count());
169 bool bFirst = true;
171 const bool bClosePath(rPolygon.isClosed());
173 sal_uInt32 nCurrentIndex = 0;
174 sal_uInt32 nPreviousIndex = nPointCount - 1;
176 basegfx::B2DPoint aCurrentPoint;
177 basegfx::B2DPoint aPreviousPoint;
179 for (sal_uInt32 nIndex = 0; nIndex <= nPointCount; nIndex++)
181 if (nIndex == nPointCount && !bClosePath)
182 continue;
184 // Make sure we loop the last point to first point
185 nCurrentIndex = nIndex % nPointCount;
186 if (bFirst)
187 bFirst = false;
188 else
190 basegfx::B2DPoint aPreviousControlPoint
191 = rPolygon.getNextControlPoint(nPreviousIndex);
192 basegfx::B2DPoint aCurrentControlPoint
193 = rPolygon.getPrevControlPoint(nCurrentIndex);
195 if (aPreviousControlPoint.equal(aPreviousPoint)
196 && aCurrentControlPoint.equal(aCurrentPoint))
198 return true; // found a straight line
201 aPreviousPoint = aCurrentPoint;
202 nPreviousIndex = nCurrentIndex;
205 return false; // no straight line found
208 SkColor toSkColor(Color color)
210 return SkColorSetARGB(color.GetAlpha(), color.GetRed(), color.GetGreen(), color.GetBlue());
213 SkColor toSkColorWithTransparency(Color aColor, double fTransparency)
215 return SkColorSetA(toSkColor(aColor), 255 * (1.0 - fTransparency));
218 SkColor toSkColorWithIntensity(Color color, int intensity)
220 return SkColorSetARGB(color.GetAlpha(), color.GetRed() * intensity / 100,
221 color.GetGreen() * intensity / 100, color.GetBlue() * intensity / 100);
224 Color fromSkColor(SkColor color)
226 return Color(ColorAlpha, SkColorGetA(color), SkColorGetR(color), SkColorGetG(color),
227 SkColorGetB(color));
230 // returns true if the source or destination rectangles are invalid
231 bool checkInvalidSourceOrDestination(SalTwoRect const& rPosAry)
233 return rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
234 || rPosAry.mnDestHeight <= 0;
237 } // end anonymous namespace
239 // Class that triggers flushing the backing buffer when idle.
240 class SkiaFlushIdle : public Idle
242 SkiaSalGraphicsImpl* mpGraphics;
243 #ifndef NDEBUG
244 char* debugname;
245 #endif
247 public:
248 explicit SkiaFlushIdle(SkiaSalGraphicsImpl* pGraphics)
249 : Idle(get_debug_name(pGraphics))
250 , mpGraphics(pGraphics)
252 // We don't want to be swapping before we've painted.
253 SetPriority(TaskPriority::POST_PAINT);
255 #ifndef NDEBUG
256 virtual ~SkiaFlushIdle() { free(debugname); }
257 #endif
258 const char* get_debug_name(SkiaSalGraphicsImpl* pGraphics)
260 #ifndef NDEBUG
261 // Idle keeps just a pointer, so we need to store the string
262 debugname = strdup(
263 OString("skia idle 0x" + OString::number(reinterpret_cast<sal_uIntPtr>(pGraphics), 16))
264 .getStr());
265 return debugname;
266 #else
267 (void)pGraphics;
268 return "skia idle";
269 #endif
272 virtual void Invoke() override
274 mpGraphics->performFlush();
275 Stop();
276 SetPriority(TaskPriority::HIGHEST);
280 SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvider* pProvider)
281 : mParent(rParent)
282 , mProvider(pProvider)
283 , mIsGPU(false)
284 , mLineColor(SALCOLOR_NONE)
285 , mFillColor(SALCOLOR_NONE)
286 , mXorMode(false)
287 , mFlush(new SkiaFlushIdle(this))
288 , mPendingOperationsToFlush(0)
289 , mScaling(1)
293 SkiaSalGraphicsImpl::~SkiaSalGraphicsImpl()
295 assert(!mSurface);
296 assert(!mWindowContext);
299 void SkiaSalGraphicsImpl::Init() {}
301 void SkiaSalGraphicsImpl::createSurface()
303 SkiaZone zone;
304 if (isOffscreen())
305 createOffscreenSurface();
306 else
307 createWindowSurface();
308 mClipRegion = vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight()));
309 mDirtyRect = SkIRect::MakeWH(GetWidth(), GetHeight());
310 setCanvasScalingAndClipping();
312 // We don't want to be swapping before we've painted.
313 mFlush->Stop();
314 mFlush->SetPriority(TaskPriority::POST_PAINT);
317 void SkiaSalGraphicsImpl::createWindowSurface(bool forceRaster)
319 SkiaZone zone;
320 assert(!isOffscreen());
321 assert(!mSurface);
322 createWindowSurfaceInternal(forceRaster);
323 if (!mSurface)
325 switch (renderMethodToUse())
327 case RenderVulkan:
328 SAL_WARN("vcl.skia",
329 "cannot create Vulkan GPU window surface, falling back to Raster");
330 destroySurface(); // destroys also WindowContext
331 return createWindowSurface(true); // try again
332 case RenderMetal:
333 SAL_WARN("vcl.skia",
334 "cannot create Metal GPU window surface, falling back to Raster");
335 destroySurface(); // destroys also WindowContext
336 return createWindowSurface(true); // try again
337 case RenderRaster:
338 abort(); // This should not really happen, do not even try to cope with it.
341 mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
342 #ifdef DBG_UTIL
343 prefillSurface(mSurface);
344 #endif
347 bool SkiaSalGraphicsImpl::isOffscreen() const
349 if (mProvider == nullptr || mProvider->IsOffScreen())
350 return true;
351 // HACK: Sometimes (tdf#131939, tdf#138022, tdf#140288) VCL passes us a zero-sized window,
352 // and zero size is invalid for Skia, so force offscreen surface, where we handle this.
353 if (GetWidth() <= 0 || GetHeight() <= 0)
354 return true;
355 return false;
358 void SkiaSalGraphicsImpl::createOffscreenSurface()
360 SkiaZone zone;
361 assert(isOffscreen());
362 assert(!mSurface);
363 // HACK: See isOffscreen().
364 int width = std::max(1, GetWidth());
365 int height = std::max(1, GetHeight());
366 // We need to use window scaling even for offscreen surfaces, because the common usage is rendering something
367 // into an offscreen surface and then copy it to a window, so without scaling here the result would be originally
368 // drawn without scaling and only upscaled when drawing to a window.
369 mScaling = getWindowScaling();
370 mSurface = createSkSurface(width * mScaling, height * mScaling);
371 assert(mSurface);
372 mIsGPU = mSurface->getCanvas()->recordingContext() != nullptr;
375 void SkiaSalGraphicsImpl::destroySurface()
377 SkiaZone zone;
378 if (mSurface)
380 // check setClipRegion() invariant
381 assert(mSurface->getCanvas()->getSaveCount() == 3);
382 // if this fails, something forgot to use SkAutoCanvasRestore
383 assert(mSurface->getCanvas()->getTotalMatrix() == SkMatrix::Scale(mScaling, mScaling));
385 // If we use e.g. Vulkan, we must destroy the surface before the context,
386 // otherwise destroying the surface will reference the context. This is
387 // handled by calling destroySurface() before destroying the context.
388 // However we also need to flush the surface before destroying it,
389 // otherwise when destroying the context later there still could be queued
390 // commands referring to the surface data. This is probably a Skia bug,
391 // but work around it here.
392 if (mSurface)
393 mSurface->flushAndSubmit();
394 mSurface.reset();
395 mWindowContext.reset();
396 mIsGPU = false;
397 mScaling = 1;
400 void SkiaSalGraphicsImpl::performFlush()
402 SkiaZone zone;
403 flushDrawing();
404 if (mSurface)
406 if (mDirtyRect.intersect(SkIRect::MakeWH(GetWidth(), GetHeight())))
407 flushSurfaceToWindowContext();
408 mDirtyRect.setEmpty();
412 void SkiaSalGraphicsImpl::flushSurfaceToWindowContext()
414 sk_sp<SkSurface> screenSurface = mWindowContext->getBackbufferSurface();
415 if (screenSurface != mSurface)
417 // GPU-based window contexts require calling getBackbufferSurface()
418 // for every swapBuffers(), for this reason mSurface is an offscreen surface
419 // where we keep the contents (LO does not do full redraws).
420 // So here blit the surface to the window context surface and then swap it.
421 assert(isGPU()); // Raster should always draw directly to backbuffer to save copying
422 SkPaint paint;
423 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
424 // We ignore mDirtyRect here, and mSurface already is in screenSurface coordinates,
425 // so no transformation needed.
426 screenSurface->getCanvas()->drawImage(makeCheckedImageSnapshot(mSurface), 0, 0,
427 SkSamplingOptions(), &paint);
428 screenSurface->flushAndSubmit(); // Otherwise the window is not drawn sometimes.
429 mWindowContext->swapBuffers(nullptr); // Must swap the entire surface.
431 else
433 // For raster mode use directly the backbuffer surface, it's just a bitmap
434 // surface anyway, and for those there's no real requirement to call
435 // getBackbufferSurface() repeatedly. Using our own surface would duplicate
436 // memory and cost time copying pixels around.
437 assert(!isGPU());
438 SkIRect dirtyRect = mDirtyRect;
439 if (mScaling != 1) // Adjust to mSurface coordinates if needed.
440 dirtyRect = scaleRect(dirtyRect, mScaling);
441 mWindowContext->swapBuffers(&dirtyRect);
445 void SkiaSalGraphicsImpl::DeInit() { destroySurface(); }
447 void SkiaSalGraphicsImpl::preDraw()
449 assert(comphelper::SolarMutex::get()->IsCurrentThread());
450 SkiaZone::enter(); // matched in postDraw()
451 checkSurface();
452 checkPendingDrawing();
455 void SkiaSalGraphicsImpl::postDraw()
457 scheduleFlush();
458 // Skia (at least when using Vulkan) queues drawing commands and executes them only later.
459 // But tdf#136369 leads to creating and queueing many tiny bitmaps, which makes
460 // Skia slow, and may make it even run out of memory. So force a flush if such
461 // a problematic operation has been performed too many times without a flush.
462 if (mPendingOperationsToFlush > 1000)
464 mSurface->flushAndSubmit();
465 mPendingOperationsToFlush = 0;
467 SkiaZone::leave(); // matched in preDraw()
468 // If there's a problem with the GPU context, abort.
469 if (GrDirectContext* context = GrAsDirectContext(mSurface->getCanvas()->recordingContext()))
471 // Running out of memory on the GPU technically could be possibly recoverable,
472 // but we don't know the exact status of the surface (and what has or has not been drawn to it),
473 // so in practice this is unrecoverable without possible data loss.
474 if (context->oomed())
476 SAL_WARN("vcl.skia", "GPU context has run out of memory, aborting.");
477 abort();
479 // Unrecoverable problem.
480 if (context->abandoned())
482 SAL_WARN("vcl.skia", "GPU context has been abandoned, aborting.");
483 abort();
488 void SkiaSalGraphicsImpl::scheduleFlush()
490 if (!isOffscreen())
492 if (!Application::IsInExecute())
493 performFlush(); // otherwise nothing would trigger idle rendering
494 else if (!mFlush->IsActive())
495 mFlush->Start();
499 // VCL can sometimes resize us without telling us, update the surface if needed.
500 // Also create the surface on demand if it has not been created yet (it is a waste
501 // to create it in Init() if it gets recreated later anyway).
502 void SkiaSalGraphicsImpl::checkSurface()
504 if (!mSurface)
506 createSurface();
507 SAL_INFO("vcl.skia.trace",
508 "create(" << this << "): " << Size(mSurface->width(), mSurface->height()));
510 else if (GetWidth() * mScaling != mSurface->width()
511 || GetHeight() * mScaling != mSurface->height())
513 if (!avoidRecreateByResize())
515 Size oldSize(mSurface->width(), mSurface->height());
516 // Recreating a surface means that the old SkSurface contents will be lost.
517 // But if a window has been resized the windowing system may send repaint events
518 // only for changed parts and VCL would not repaint the whole area, assuming
519 // that some parts have not changed (this is what seems to cause tdf#131952).
520 // So carry over the old contents for windows, even though generally everything
521 // will be usually repainted anyway.
522 sk_sp<SkImage> snapshot;
523 if (!isOffscreen())
525 flushDrawing();
526 snapshot = makeCheckedImageSnapshot(mSurface);
529 destroySurface();
530 createSurface();
532 if (snapshot)
534 SkPaint paint;
535 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
536 // Scaling by current mScaling is active, undo that. We assume that the scaling
537 // does not change.
538 resetCanvasScalingAndClipping();
539 mSurface->getCanvas()->drawImage(snapshot, 0, 0, SkSamplingOptions(), &paint);
540 setCanvasScalingAndClipping();
542 SAL_INFO("vcl.skia.trace", "recreate(" << this << "): old " << oldSize << " new "
543 << Size(mSurface->width(), mSurface->height())
544 << " requested "
545 << Size(GetWidth(), GetHeight()));
550 bool SkiaSalGraphicsImpl::avoidRecreateByResize() const
552 // Keep the old surface if VCL sends us a broken size (see isOffscreen()).
553 if (GetWidth() == 0 || GetHeight() == 0)
554 return true;
555 return false;
558 void SkiaSalGraphicsImpl::flushDrawing()
560 if (!mSurface)
561 return;
562 checkPendingDrawing();
563 if (mXorMode)
564 applyXor();
565 mSurface->flushAndSubmit();
566 mPendingOperationsToFlush = 0;
569 void SkiaSalGraphicsImpl::setCanvasScalingAndClipping()
571 SkCanvas* canvas = mSurface->getCanvas();
572 assert(canvas->getSaveCount() == 1);
573 // If HiDPI scaling is active, simply set a scaling matrix for the canvas. This means
574 // that all painting can use VCL coordinates and they'll be automatically translated to mSurface
575 // scaled coordinates. If that is not wanted, the scale() state needs to be temporarily unset.
576 // State such as mDirtyRect and mXorRegion is not scaled, the scaling matrix applies to clipping too,
577 // and the rest needs to be handled explicitly.
578 // When reading mSurface contents there's no automatic scaling and it needs to be handled explicitly.
579 canvas->save(); // keep the original state without any scaling
580 canvas->scale(mScaling, mScaling);
582 // SkCanvas::clipRegion() can only further reduce the clip region,
583 // but we need to set the given region, which may extend it.
584 // So handle that by always having the full clip region saved on the stack
585 // and always go back to that. SkCanvas::restore() only affects the clip
586 // and the matrix.
587 canvas->save(); // keep scaled state without clipping
588 setCanvasClipRegion(canvas, mClipRegion);
591 void SkiaSalGraphicsImpl::resetCanvasScalingAndClipping()
593 SkCanvas* canvas = mSurface->getCanvas();
594 assert(canvas->getSaveCount() == 3);
595 canvas->restore(); // undo clipping
596 canvas->restore(); // undo scaling
599 bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
601 if (mClipRegion == region)
602 return true;
603 SkiaZone zone;
604 checkPendingDrawing();
605 checkSurface();
606 mClipRegion = region;
607 SAL_INFO("vcl.skia.trace", "setclipregion(" << this << "): " << region);
608 SkCanvas* canvas = mSurface->getCanvas();
609 assert(canvas->getSaveCount() == 3);
610 canvas->restore(); // undo previous clip state, see setCanvasScalingAndClipping()
611 canvas->save();
612 setCanvasClipRegion(canvas, region);
613 return true;
616 void SkiaSalGraphicsImpl::setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region)
618 SkiaZone zone;
619 SkPath path;
620 // Always use region rectangles, regardless of what the region uses internally.
621 // That's what other VCL backends do, and trying to use addPolyPolygonToPath()
622 // in case a polygon is used leads to off-by-one errors such as tdf#133208.
623 RectangleVector rectangles;
624 region.GetRegionRectangles(rectangles);
625 path.incReserve(rectangles.size() + 1);
626 for (const tools::Rectangle& rectangle : rectangles)
627 path.addRect(SkRect::MakeXYWH(rectangle.getX(), rectangle.getY(), rectangle.GetWidth(),
628 rectangle.GetHeight()));
629 path.setFillType(SkPathFillType::kEvenOdd);
630 canvas->clipPath(path);
633 void SkiaSalGraphicsImpl::ResetClipRegion()
635 setClipRegion(vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight())));
638 const vcl::Region& SkiaSalGraphicsImpl::getClipRegion() const { return mClipRegion; }
640 sal_uInt16 SkiaSalGraphicsImpl::GetBitCount() const { return 32; }
642 tools::Long SkiaSalGraphicsImpl::GetGraphicsWidth() const { return GetWidth(); }
644 void SkiaSalGraphicsImpl::SetLineColor()
646 checkPendingDrawing();
647 mLineColor = SALCOLOR_NONE;
650 void SkiaSalGraphicsImpl::SetLineColor(Color nColor)
652 checkPendingDrawing();
653 mLineColor = nColor;
656 void SkiaSalGraphicsImpl::SetFillColor()
658 checkPendingDrawing();
659 mFillColor = SALCOLOR_NONE;
662 void SkiaSalGraphicsImpl::SetFillColor(Color nColor)
664 checkPendingDrawing();
665 mFillColor = nColor;
668 void SkiaSalGraphicsImpl::SetXORMode(bool set, bool)
670 if (mXorMode == set)
671 return;
672 checkPendingDrawing();
673 SAL_INFO("vcl.skia.trace", "setxormode(" << this << "): " << set);
674 if (set)
675 mXorRegion.setEmpty();
676 else
677 applyXor();
678 mXorMode = set;
681 SkCanvas* SkiaSalGraphicsImpl::getXorCanvas()
683 SkiaZone zone;
684 assert(mXorMode);
685 // Skia does not implement xor drawing, so we need to handle it manually by redirecting
686 // to a temporary SkBitmap and then doing the xor operation on the data ourselves.
687 // There's no point in using SkSurface for GPU, we'd immediately need to get the pixels back.
688 if (!mXorCanvas)
690 // Use unpremultiplied alpha (see xor applying in applyXor()).
691 if (!mXorBitmap.tryAllocPixels(mSurface->imageInfo().makeAlphaType(kUnpremul_SkAlphaType)))
692 abort();
693 mXorBitmap.eraseARGB(0, 0, 0, 0);
694 mXorCanvas = std::make_unique<SkCanvas>(mXorBitmap);
695 if (mScaling != 1)
696 mXorCanvas->scale(mScaling, mScaling);
697 setCanvasClipRegion(mXorCanvas.get(), mClipRegion);
699 return mXorCanvas.get();
702 void SkiaSalGraphicsImpl::applyXor()
704 // Apply the result from the temporary bitmap manually. This is indeed
705 // slow, but it doesn't seem to be needed often and is optimized
706 // in each operation by extending mXorRegion with the area that should be
707 // updated.
708 assert(mXorMode);
709 if (mScaling != 1 && !mXorRegion.isEmpty())
711 // Scale mXorRegion to mSurface coordinates if needed.
712 std::vector<SkIRect> rects;
713 for (SkRegion::Iterator it(mXorRegion); !it.done(); it.next())
714 rects.push_back(scaleRect(it.rect(), mScaling));
715 mXorRegion.setRects(rects.data(), rects.size());
717 if (!mSurface || !mXorCanvas
718 || !mXorRegion.op(SkIRect::MakeXYWH(0, 0, mSurface->width(), mSurface->height()),
719 SkRegion::kIntersect_Op))
721 mXorRegion.setEmpty();
722 return;
724 SAL_INFO("vcl.skia.trace", "applyxor(" << this << "): " << mXorRegion);
725 // Copy the surface contents to another pixmap.
726 SkBitmap surfaceBitmap;
727 // Use unpremultiplied alpha format, so that we do not have to do the conversions to get
728 // the RGB and back (Skia will do it when converting, but it'll be presumably faster at it).
729 if (!surfaceBitmap.tryAllocPixels(mSurface->imageInfo().makeAlphaType(kUnpremul_SkAlphaType)))
730 abort();
731 SkPaint paint;
732 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
733 SkRect area = SkRect::Make(mXorRegion.getBounds());
735 SkCanvas canvas(surfaceBitmap);
736 canvas.drawImageRect(makeCheckedImageSnapshot(mSurface), area, area, SkSamplingOptions(),
737 &paint, SkCanvas::kFast_SrcRectConstraint);
739 // xor to surfaceBitmap
740 assert(surfaceBitmap.info().alphaType() == kUnpremul_SkAlphaType);
741 assert(mXorBitmap.info().alphaType() == kUnpremul_SkAlphaType);
742 assert(surfaceBitmap.bytesPerPixel() == 4);
743 assert(mXorBitmap.bytesPerPixel() == 4);
744 for (SkRegion::Iterator it(mXorRegion); !it.done(); it.next())
746 for (int y = it.rect().top(); y < it.rect().bottom(); ++y)
748 uint8_t* data = static_cast<uint8_t*>(surfaceBitmap.getAddr(it.rect().x(), y));
749 const uint8_t* xordata = static_cast<uint8_t*>(mXorBitmap.getAddr(it.rect().x(), y));
750 for (int x = 0; x < it.rect().width(); ++x)
752 *data++ ^= *xordata++;
753 *data++ ^= *xordata++;
754 *data++ ^= *xordata++;
755 // alpha is not xor-ed
756 data++;
757 xordata++;
761 surfaceBitmap.notifyPixelsChanged();
762 surfaceBitmap.setImmutable();
763 // Copy without any clipping or scaling.
764 resetCanvasScalingAndClipping();
765 mSurface->getCanvas()->drawImageRect(surfaceBitmap.asImage(), area, area, SkSamplingOptions(),
766 &paint, SkCanvas::kFast_SrcRectConstraint);
767 setCanvasScalingAndClipping();
768 mXorCanvas.reset();
769 mXorBitmap.reset();
770 mXorRegion.setEmpty();
773 void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor)
775 checkPendingDrawing();
776 switch (nROPColor)
778 case SalROPColor::N0:
779 mLineColor = Color(0, 0, 0);
780 break;
781 case SalROPColor::N1:
782 mLineColor = Color(0xff, 0xff, 0xff);
783 break;
784 case SalROPColor::Invert:
785 mLineColor = Color(0xff, 0xff, 0xff);
786 break;
790 void SkiaSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor)
792 checkPendingDrawing();
793 switch (nROPColor)
795 case SalROPColor::N0:
796 mFillColor = Color(0, 0, 0);
797 break;
798 case SalROPColor::N1:
799 mFillColor = Color(0xff, 0xff, 0xff);
800 break;
801 case SalROPColor::Invert:
802 mFillColor = Color(0xff, 0xff, 0xff);
803 break;
807 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY)
809 drawPixel(nX, nY, mLineColor);
812 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
814 if (nColor == SALCOLOR_NONE)
815 return;
816 preDraw();
817 SAL_INFO("vcl.skia.trace", "drawpixel(" << this << "): " << Point(nX, nY) << ":" << nColor);
818 addUpdateRegion(SkRect::MakeXYWH(nX, nY, 1, 1));
819 SkPaint paint;
820 paint.setColor(toSkColor(nColor));
821 // Apparently drawPixel() is actually expected to set the pixel and not draw it.
822 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
823 if (mScaling != 1 && isUnitTestRunning())
825 // On HiDPI displays, draw a square on the entire non-hidpi "pixel" when running unittests,
826 // since tests often require precise pixel drawing.
827 paint.setStrokeWidth(1); // this will be scaled by mScaling
828 paint.setStrokeCap(SkPaint::kSquare_Cap);
830 getDrawCanvas()->drawPoint(toSkX(nX), toSkY(nY), paint);
831 postDraw();
834 void SkiaSalGraphicsImpl::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
835 tools::Long nY2)
837 if (mLineColor == SALCOLOR_NONE)
838 return;
839 preDraw();
840 SAL_INFO("vcl.skia.trace", "drawline(" << this << "): " << Point(nX1, nY1) << "->"
841 << Point(nX2, nY2) << ":" << mLineColor);
842 addUpdateRegion(SkRect::MakeLTRB(nX1, nY1, nX2, nY2).makeSorted());
843 SkPaint paint;
844 paint.setColor(toSkColor(mLineColor));
845 paint.setAntiAlias(mParent.getAntiAlias());
846 if (mScaling != 1 && isUnitTestRunning())
848 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
849 // smoothing that would confuse unittests.
850 paint.setStrokeWidth(1); // this will be scaled by mScaling
851 paint.setStrokeCap(SkPaint::kSquare_Cap);
853 getDrawCanvas()->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
854 postDraw();
857 void SkiaSalGraphicsImpl::privateDrawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
858 tools::Long nHeight, double fTransparency,
859 bool blockAA)
861 preDraw();
862 SAL_INFO("vcl.skia.trace",
863 "privatedrawrect(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight)
864 << ":" << mLineColor << ":" << mFillColor << ":" << fTransparency);
865 addUpdateRegion(SkRect::MakeXYWH(nX, nY, nWidth, nHeight));
866 SkCanvas* canvas = getDrawCanvas();
867 SkPaint paint;
868 paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
869 if (mFillColor != SALCOLOR_NONE)
871 paint.setColor(toSkColorWithTransparency(mFillColor, fTransparency));
872 paint.setStyle(SkPaint::kFill_Style);
873 // HACK: If the polygon is just a line, it still should be drawn. But when filling
874 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
875 if (mLineColor == SALCOLOR_NONE && SkSize::Make(nWidth, nHeight).isEmpty())
876 paint.setStyle(SkPaint::kStroke_Style);
877 canvas->drawIRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), paint);
879 if (mLineColor != SALCOLOR_NONE)
881 paint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
882 paint.setStyle(SkPaint::kStroke_Style);
883 if (mScaling != 1 && isUnitTestRunning())
885 // On HiDPI displays, do not draw just a harline but instead a full-width "pixel" when running unittests,
886 // since tests often require precise pixel drawing.
887 paint.setStrokeWidth(1); // this will be scaled by mScaling
888 paint.setStrokeCap(SkPaint::kSquare_Cap);
890 // The obnoxious "-1 DrawRect()" hack that I don't understand the purpose of (and I'm not sure
891 // if anybody does), but without it some cases do not work. The max() is needed because Skia
892 // will not draw anything if width or height is 0.
893 canvas->drawRect(SkRect::MakeXYWH(toSkX(nX), toSkY(nY),
894 std::max(tools::Long(1), nWidth - 1),
895 std::max(tools::Long(1), nHeight - 1)),
896 paint);
898 postDraw();
901 void SkiaSalGraphicsImpl::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
902 tools::Long nHeight)
904 privateDrawAlphaRect(nX, nY, nWidth, nHeight, 0.0, true);
907 void SkiaSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
909 basegfx::B2DPolygon aPolygon;
910 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
911 for (sal_uInt32 i = 1; i < nPoints; ++i)
912 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
913 aPolygon.setClosed(false);
915 drawPolyLine(basegfx::B2DHomMatrix(), aPolygon, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
916 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false);
919 void SkiaSalGraphicsImpl::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
921 basegfx::B2DPolygon aPolygon;
922 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
923 for (sal_uInt32 i = 1; i < nPoints; ++i)
924 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
926 drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPolygon), 0.0);
929 void SkiaSalGraphicsImpl::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
930 const Point** pPtAry)
932 basegfx::B2DPolyPolygon aPolyPolygon;
933 for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
935 sal_uInt32 nPoints = pPoints[nPolygon];
936 if (nPoints)
938 const Point* pSubPoints = pPtAry[nPolygon];
939 basegfx::B2DPolygon aPolygon;
940 aPolygon.append(basegfx::B2DPoint(pSubPoints->getX(), pSubPoints->getY()), nPoints);
941 for (sal_uInt32 i = 1; i < nPoints; ++i)
942 aPolygon.setB2DPoint(i,
943 basegfx::B2DPoint(pSubPoints[i].getX(), pSubPoints[i].getY()));
945 aPolyPolygon.append(aPolygon);
949 drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon, 0.0);
952 bool SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
953 const basegfx::B2DPolyPolygon& rPolyPolygon,
954 double fTransparency)
956 const bool bHasFill(mFillColor != SALCOLOR_NONE);
957 const bool bHasLine(mLineColor != SALCOLOR_NONE);
959 if (rPolyPolygon.count() == 0 || !(bHasFill || bHasLine) || fTransparency < 0.0
960 || fTransparency >= 1.0)
961 return true;
963 basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
964 aPolyPolygon.transform(rObjectToDevice);
966 SAL_INFO("vcl.skia.trace", "drawpolypolygon(" << this << "): " << aPolyPolygon << ":"
967 << mLineColor << ":" << mFillColor);
969 if (delayDrawPolyPolygon(aPolyPolygon, fTransparency))
971 scheduleFlush();
972 return true;
975 performDrawPolyPolygon(aPolyPolygon, fTransparency, mParent.getAntiAlias());
976 return true;
979 void SkiaSalGraphicsImpl::performDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
980 double fTransparency, bool useAA)
982 preDraw();
984 SkPath polygonPath;
985 bool hasOnlyOrthogonal = true;
986 addPolyPolygonToPath(aPolyPolygon, polygonPath, &hasOnlyOrthogonal);
987 polygonPath.setFillType(SkPathFillType::kEvenOdd);
988 addUpdateRegion(polygonPath.getBounds());
990 SkPaint aPaint;
991 aPaint.setAntiAlias(useAA);
993 // For lines we use toSkX()/toSkY() in order to pass centers of pixels to Skia,
994 // as that leads to better results with floating-point coordinates
995 // (e.g. https://bugs.chromium.org/p/skia/issues/detail?id=9611).
996 // But that means that we generally need to use it also for areas, so that they
997 // line up properly if used together (tdf#134346).
998 // On the other hand, with AA enabled and rectangular areas, this leads to fuzzy
999 // edges (tdf#137329). But since rectangular areas line up perfectly to pixels
1000 // everywhere, it shouldn't be necessary to do this for them.
1001 // So if AA is enabled, avoid this fixup for rectangular areas.
1002 if (!useAA || !hasOnlyOrthogonal)
1004 // We normally use pixel at their center positions, but slightly off (see toSkX/Y()).
1005 // With AA lines that "slightly off" causes tiny changes of color, making some tests
1006 // fail. Since moving AA-ed line slightly to a side doesn't cause any real visual
1007 // difference, just place exactly at the center. tdf#134346
1008 const SkScalar posFix = useAA ? toSkXYFix : 0;
1009 polygonPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1011 if (mFillColor != SALCOLOR_NONE)
1013 aPaint.setColor(toSkColorWithTransparency(mFillColor, fTransparency));
1014 aPaint.setStyle(SkPaint::kFill_Style);
1015 // HACK: If the polygon is just a line, it still should be drawn. But when filling
1016 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
1017 if (mLineColor == SALCOLOR_NONE && polygonPath.getBounds().isEmpty())
1018 aPaint.setStyle(SkPaint::kStroke_Style);
1019 getDrawCanvas()->drawPath(polygonPath, aPaint);
1021 if (mLineColor != SALCOLOR_NONE)
1023 aPaint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
1024 aPaint.setStyle(SkPaint::kStroke_Style);
1025 getDrawCanvas()->drawPath(polygonPath, aPaint);
1027 postDraw();
1028 #if defined LINUX
1029 // WORKAROUND: The logo in the about dialog has drawing errors. This seems to happen
1030 // only on Linux (not Windows on the same machine), with both AMDGPU and Mesa,
1031 // and only when antialiasing is enabled. Flushing seems to avoid the problem.
1032 if (useAA && getVendor() == DriverBlocklist::VendorAMD)
1033 mSurface->flushAndSubmit();
1034 #endif
1037 namespace
1039 struct LessThan
1041 bool operator()(const basegfx::B2DPoint& point1, const basegfx::B2DPoint& point2) const
1043 if (basegfx::fTools::equal(point1.getX(), point2.getX()))
1044 return basegfx::fTools::less(point1.getY(), point2.getY());
1045 return basegfx::fTools::less(point1.getX(), point2.getX());
1048 } // namespace
1050 bool SkiaSalGraphicsImpl::delayDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
1051 double fTransparency)
1053 // There is some code that needlessly subdivides areas into adjacent rectangles,
1054 // but Skia doesn't line them up perfectly if AA is enabled (e.g. Cairo, Qt5 do,
1055 // but Skia devs claim it's working as intended
1056 // https://groups.google.com/d/msg/skia-discuss/NlKpD2X_5uc/Vuwd-kyYBwAJ).
1057 // An example is tdf#133016, which triggers SvgStyleAttributes::add_stroke()
1058 // implementing a line stroke as a bunch of polygons instead of just one, and
1059 // SvgLinearAtomPrimitive2D::create2DDecomposition() creates a gradient
1060 // as a series of polygons of gradually changing color. Those places should be
1061 // changed, but try to merge those split polygons back into the original one,
1062 // where the needlessly created edges causing problems will not exist.
1063 // This means drawing of such polygons needs to be delayed, so that they can
1064 // be possibly merged with the next one.
1065 // Merge only polygons of the same properties (color, etc.), so the gradient problem
1066 // actually isn't handled here.
1068 // Only AA polygons need merging, because they do not line up well because of the AA of the edges.
1069 if (!mParent.getAntiAlias())
1070 return false;
1071 // Only filled polygons without an outline are problematic.
1072 if (mFillColor == SALCOLOR_NONE || mLineColor != SALCOLOR_NONE)
1073 return false;
1074 // Merge only simple polygons, real polypolygons most likely aren't needlessly split,
1075 // so they do not need joining.
1076 if (aPolyPolygon.count() != 1)
1077 return false;
1078 // If the polygon is not closed, it doesn't mark an area to be filled.
1079 if (!aPolyPolygon.isClosed())
1080 return false;
1081 // If a polygon does not contain a straight line, i.e. it's all curves, then do not merge.
1082 // First of all that's even more expensive, and second it's very unlikely that it's a polygon
1083 // split into more polygons.
1084 if (!polygonContainsLine(aPolyPolygon))
1085 return false;
1087 if (mLastPolyPolygonInfo.polygons.size() != 0
1088 && (mLastPolyPolygonInfo.transparency != fTransparency
1089 || !mLastPolyPolygonInfo.bounds.overlaps(aPolyPolygon.getB2DRange())))
1091 checkPendingDrawing(); // Cannot be parts of the same larger polygon, draw the last and reset.
1093 if (!mLastPolyPolygonInfo.polygons.empty())
1095 assert(aPolyPolygon.count() == 1);
1096 assert(mLastPolyPolygonInfo.polygons.back().count() == 1);
1097 // Check if the new and the previous polygon share at least one point. If not, then they
1098 // cannot be adjacent polygons, so there's no point in trying to merge them.
1099 bool sharePoint = false;
1100 const basegfx::B2DPolygon& poly1 = aPolyPolygon.getB2DPolygon(0);
1101 const basegfx::B2DPolygon& poly2 = mLastPolyPolygonInfo.polygons.back().getB2DPolygon(0);
1102 o3tl::sorted_vector<basegfx::B2DPoint, LessThan> poly1Points; // for O(n log n)
1103 poly1Points.reserve(poly1.count());
1104 for (sal_uInt32 i = 0; i < poly1.count(); ++i)
1105 poly1Points.insert(poly1.getB2DPoint(i));
1106 for (sal_uInt32 i = 0; i < poly2.count(); ++i)
1107 if (poly1Points.find(poly2.getB2DPoint(i)) != poly1Points.end())
1109 sharePoint = true;
1110 break;
1112 if (!sharePoint)
1113 checkPendingDrawing(); // Draw the previous one and reset.
1115 // Collect the polygons that can be possibly merged. Do the merging only once at the end,
1116 // because it's not a cheap operation.
1117 mLastPolyPolygonInfo.polygons.push_back(aPolyPolygon);
1118 mLastPolyPolygonInfo.bounds.expand(aPolyPolygon.getB2DRange());
1119 mLastPolyPolygonInfo.transparency = fTransparency;
1120 return true;
1123 // Tdf#140848 - basegfx::utils::mergeToSinglePolyPolygon() seems to have rounding
1124 // errors that sometimes cause it to merge incorrectly.
1125 static void roundPolygonPoints(basegfx::B2DPolyPolygon& polyPolygon)
1127 for (basegfx::B2DPolygon& polygon : polyPolygon)
1129 polygon.makeUnique();
1130 for (sal_uInt32 i = 0; i < polygon.count(); ++i)
1131 polygon.setB2DPoint(i, basegfx::B2DPoint(basegfx::fround(polygon.getB2DPoint(i))));
1132 // Control points are saved as vectors relative to points, so hopefully
1133 // there's no need to round those.
1137 void SkiaSalGraphicsImpl::checkPendingDrawing()
1139 if (mLastPolyPolygonInfo.polygons.size() != 0)
1140 { // Flush any pending polygon drawing.
1141 basegfx::B2DPolyPolygonVector polygons;
1142 std::swap(polygons, mLastPolyPolygonInfo.polygons);
1143 double transparency = mLastPolyPolygonInfo.transparency;
1144 mLastPolyPolygonInfo.bounds.reset();
1145 if (polygons.size() == 1)
1146 performDrawPolyPolygon(polygons.front(), transparency, true);
1147 else
1149 for (basegfx::B2DPolyPolygon& p : polygons)
1150 roundPolygonPoints(p);
1151 performDrawPolyPolygon(basegfx::utils::mergeToSinglePolyPolygon(polygons), transparency,
1152 true);
1157 bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
1158 const basegfx::B2DPolygon& rPolyLine, double fTransparency,
1159 double fLineWidth, const std::vector<double>* pStroke,
1160 basegfx::B2DLineJoin eLineJoin,
1161 css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
1162 bool bPixelSnapHairline)
1164 if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0
1165 || mLineColor == SALCOLOR_NONE)
1167 return true;
1170 preDraw();
1171 SAL_INFO("vcl.skia.trace", "drawpolyline(" << this << "): " << rPolyLine << ":" << mLineColor);
1173 // Adjust line width for object-to-device scale.
1174 fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
1175 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
1176 // smoothing that would confuse unittests.
1177 if (fLineWidth == 0 && mScaling != 1 && isUnitTestRunning())
1178 fLineWidth = 1; // this will be scaled by mScaling
1180 // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
1181 basegfx::B2DPolygon aPolyLine(rPolyLine);
1182 aPolyLine.transform(rObjectToDevice);
1183 if (bPixelSnapHairline)
1185 aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);
1188 // Setup Line Join
1189 SkPaint::Join eSkLineJoin = SkPaint::kMiter_Join;
1190 switch (eLineJoin)
1192 case basegfx::B2DLineJoin::Bevel:
1193 eSkLineJoin = SkPaint::kBevel_Join;
1194 break;
1195 case basegfx::B2DLineJoin::Round:
1196 eSkLineJoin = SkPaint::kRound_Join;
1197 break;
1198 case basegfx::B2DLineJoin::NONE:
1199 case basegfx::B2DLineJoin::Miter:
1200 eSkLineJoin = SkPaint::kMiter_Join;
1201 break;
1204 // convert miter minimum angle to miter limit
1205 double fMiterLimit = 1.0 / std::sin(fMiterMinimumAngle / 2.0);
1207 // Setup Line Cap
1208 SkPaint::Cap eSkLineCap(SkPaint::kButt_Cap);
1210 switch (eLineCap)
1212 case css::drawing::LineCap_ROUND:
1213 eSkLineCap = SkPaint::kRound_Cap;
1214 break;
1215 case css::drawing::LineCap_SQUARE:
1216 eSkLineCap = SkPaint::kSquare_Cap;
1217 break;
1218 default: // css::drawing::LineCap_BUTT:
1219 eSkLineCap = SkPaint::kButt_Cap;
1220 break;
1223 SkPaint aPaint;
1224 aPaint.setStyle(SkPaint::kStroke_Style);
1225 aPaint.setStrokeCap(eSkLineCap);
1226 aPaint.setStrokeJoin(eSkLineJoin);
1227 aPaint.setColor(toSkColorWithTransparency(mLineColor, fTransparency));
1228 aPaint.setStrokeMiter(fMiterLimit);
1229 aPaint.setStrokeWidth(fLineWidth);
1230 aPaint.setAntiAlias(mParent.getAntiAlias());
1231 // See the tdf#134346 comment above.
1232 const SkScalar posFix = mParent.getAntiAlias() ? toSkXYFix : 0;
1234 if (pStroke && std::accumulate(pStroke->begin(), pStroke->end(), 0.0) != 0)
1236 std::vector<SkScalar> intervals;
1237 // Transform size by the matrix.
1238 for (double stroke : *pStroke)
1239 intervals.push_back((rObjectToDevice * basegfx::B2DVector(stroke, 0)).getLength());
1240 aPaint.setPathEffect(SkDashPathEffect::Make(intervals.data(), intervals.size(), 0));
1243 // Skia does not support basegfx::B2DLineJoin::NONE, so in that case batch only if lines
1244 // are not wider than a pixel.
1245 if (eLineJoin != basegfx::B2DLineJoin::NONE || fLineWidth <= 1.0)
1247 SkPath aPath;
1248 aPath.incReserve(aPolyLine.count() * 3); // because cubicTo is 3 elements
1249 aPath.setFillType(SkPathFillType::kEvenOdd);
1250 addPolygonToPath(aPolyLine, aPath);
1251 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1252 addUpdateRegion(aPath.getBounds());
1253 getDrawCanvas()->drawPath(aPath, aPaint);
1255 else
1257 sal_uInt32 nPoints = aPolyLine.count();
1258 bool bClosed = aPolyLine.isClosed();
1259 for (sal_uInt32 j = 0; j < (bClosed ? nPoints : nPoints - 1); ++j)
1261 sal_uInt32 index1 = (j + 0) % nPoints;
1262 sal_uInt32 index2 = (j + 1) % nPoints;
1263 SkPath aPath;
1264 aPath.moveTo(aPolyLine.getB2DPoint(index1).getX(),
1265 aPolyLine.getB2DPoint(index1).getY());
1266 aPath.lineTo(aPolyLine.getB2DPoint(index2).getX(),
1267 aPolyLine.getB2DPoint(index2).getY());
1269 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1270 addUpdateRegion(aPath.getBounds());
1271 getDrawCanvas()->drawPath(aPath, aPaint);
1275 postDraw();
1277 return true;
1280 bool SkiaSalGraphicsImpl::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*)
1282 return false;
1285 bool SkiaSalGraphicsImpl::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*)
1287 return false;
1290 bool SkiaSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*, const Point* const*,
1291 const PolyFlags* const*)
1293 return false;
1296 void SkiaSalGraphicsImpl::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
1297 tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
1298 bool /*bWindowInvalidate*/)
1300 if (nDestX == nSrcX && nDestY == nSrcY)
1301 return;
1302 preDraw();
1303 SAL_INFO("vcl.skia.trace", "copyarea("
1304 << this << "): " << Point(nSrcX, nSrcY) << "->"
1305 << SkIRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
1306 // Using SkSurface::draw() should be more efficient, but it's too buggy.
1307 SalTwoRect rPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
1308 privateCopyBits(rPosAry, this);
1309 postDraw();
1312 void SkiaSalGraphicsImpl::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
1314 preDraw();
1315 SkiaSalGraphicsImpl* src;
1316 if (pSrcGraphics)
1318 assert(dynamic_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl()));
1319 src = static_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl());
1320 src->checkSurface();
1321 src->flushDrawing();
1323 else
1325 src = this;
1326 assert(!mXorMode);
1328 auto srcDebug = [&]() -> std::string {
1329 if (src == this)
1330 return "(self)";
1331 else
1333 std::ostringstream stream;
1334 stream << "(" << src << ")";
1335 return stream.str();
1338 SAL_INFO("vcl.skia.trace", "copybits(" << this << "): " << srcDebug() << ": " << rPosAry);
1339 privateCopyBits(rPosAry, src);
1340 postDraw();
1343 void SkiaSalGraphicsImpl::privateCopyBits(const SalTwoRect& rPosAry, SkiaSalGraphicsImpl* src)
1345 assert(!mXorMode);
1346 addUpdateRegion(SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1347 rPosAry.mnDestHeight));
1348 SkPaint paint;
1349 paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
1350 SkRect srcRect
1351 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1352 SkRect destRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1353 rPosAry.mnDestHeight);
1354 // Scaling for source coordinates must be done manually.
1355 if (src->mScaling != 1)
1356 srcRect = scaleRect(srcRect, src->mScaling);
1357 // Do not use makeImageSnapshot(rect), as that one may make a needless data copy.
1358 getDrawCanvas()->drawImageRect(makeCheckedImageSnapshot(src->mSurface), srcRect, destRect,
1359 makeSamplingOptions(rPosAry, mScaling, src->mScaling), &paint,
1360 SkCanvas::kFast_SrcRectConstraint);
1363 bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect& rPosAry, const SalBitmap& rBitmap)
1365 if (checkInvalidSourceOrDestination(rPosAry))
1366 return false;
1368 assert(dynamic_cast<const SkiaSalBitmap*>(&rBitmap));
1369 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rBitmap);
1370 // This is used by VirtualDevice in the alpha mode for the "alpha" layer which
1371 // is actually one-minus-alpha (opacity). Therefore white=0xff=transparent,
1372 // black=0x00=opaque. So the result is transparent only if both the inputs
1373 // are transparent. Since for blending operations white=1.0 and black=0.0,
1374 // kMultiply should handle exactly that (transparent*transparent=transparent,
1375 // opaque*transparent=opaque). And guessing from the "floor" in TYPE_BLEND in opengl's
1376 // combinedTextureFragmentShader.glsl, the layer is not even alpha values but
1377 // simply yes-or-no mask.
1378 // See also blendAlphaBitmap().
1379 if (rSkiaBitmap.IsFullyOpaqueAsAlpha())
1381 // Optimization. If the bitmap means fully opaque, it's all zero's. In CPU
1382 // mode it should be faster to just copy instead of SkBlendMode::kMultiply.
1383 drawBitmap(rPosAry, rSkiaBitmap);
1385 else
1386 drawBitmap(rPosAry, rSkiaBitmap, SkBlendMode::kMultiply);
1387 return true;
1390 bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry,
1391 const SalBitmap& rSourceBitmap,
1392 const SalBitmap& rMaskBitmap,
1393 const SalBitmap& rAlphaBitmap)
1395 if (checkInvalidSourceOrDestination(rPosAry))
1396 return false;
1398 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1399 assert(dynamic_cast<const SkiaSalBitmap*>(&rMaskBitmap));
1400 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1401 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1402 const SkiaSalBitmap& rSkiaMaskBitmap = static_cast<const SkiaSalBitmap&>(rMaskBitmap);
1403 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1405 if (rSkiaMaskBitmap.IsFullyOpaqueAsAlpha())
1407 // Optimization. If the mask of the bitmap to be blended means it's actually opaque,
1408 // just draw the bitmap directly (that's what the math below will result in).
1409 drawBitmap(rPosAry, rSkiaSourceBitmap);
1410 return true;
1412 // This was originally implemented for the OpenGL drawing method and it is poorly documented.
1413 // The source and mask bitmaps are the usual data and alpha bitmaps, and 'alpha'
1414 // is the "alpha" layer of the VirtualDevice (the alpha in VirtualDevice is also stored
1415 // as a separate bitmap). Now if I understand it correctly these two alpha masks first need
1416 // to be combined into the actual alpha mask to be used. The formula for TYPE_BLEND
1417 // in opengl's combinedTextureFragmentShader.glsl is
1418 // "result_alpha = 1.0 - (1.0 - floor(alpha)) * mask".
1419 // See also blendBitmap().
1421 SkSamplingOptions samplingOptions = makeSamplingOptions(rPosAry, mScaling);
1422 // First do the "( 1 - alpha ) * mask"
1423 // (no idea how to do "floor", but hopefully not needed in practice).
1424 sk_sp<SkShader> shaderAlpha
1425 = SkShaders::Blend(SkBlendMode::kDstOut, rSkiaMaskBitmap.GetAlphaSkShader(samplingOptions),
1426 rSkiaAlphaBitmap.GetAlphaSkShader(samplingOptions));
1427 // And now draw the bitmap with "1 - x", where x is the "( 1 - alpha ) * mask".
1428 sk_sp<SkShader> shader = SkShaders::Blend(SkBlendMode::kSrcOut, shaderAlpha,
1429 rSkiaSourceBitmap.GetSkShader(samplingOptions));
1430 drawShader(rPosAry, shader);
1431 return true;
1434 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
1436 if (checkInvalidSourceOrDestination(rPosAry))
1437 return;
1439 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1440 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1442 drawBitmap(rPosAry, rSkiaSourceBitmap);
1445 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1446 const SalBitmap& rMaskBitmap)
1448 drawAlphaBitmap(rPosAry, rSalBitmap, rMaskBitmap);
1451 void SkiaSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1452 Color nMaskColor)
1454 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1455 const SkiaSalBitmap& skiaBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1456 drawShader(
1457 rPosAry,
1458 SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1459 SkShaders::Color(toSkColor(nMaskColor)),
1460 skiaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
1463 std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(tools::Long nX, tools::Long nY,
1464 tools::Long nWidth, tools::Long nHeight)
1466 SkiaZone zone;
1467 checkSurface();
1468 SAL_INFO("vcl.skia.trace",
1469 "getbitmap(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight));
1470 flushDrawing();
1471 // TODO makeImageSnapshot(rect) may copy the data, which may be a waste if this is used
1472 // e.g. for VirtualDevice's lame alpha blending, in which case the image will eventually end up
1473 // in blendAlphaBitmap(), where we could simply use the proper rect of the image.
1474 sk_sp<SkImage> image = makeCheckedImageSnapshot(
1475 mSurface, scaleRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), mScaling));
1476 std::shared_ptr<SkiaSalBitmap> bitmap = std::make_shared<SkiaSalBitmap>(image);
1477 // TODO: If the surface is scaled for HiDPI, the bitmap needs to be scaled down, otherwise
1478 // it would have incorrect size from the API point of view. This could lead to loss of quality
1479 // if the bitmap is drawn to another scaled surface. Since the bitmap scaling is done only
1480 // on-demand, this state should be detected when drawing the bitmap and the scaling
1481 // should be ignored.
1482 if (mScaling != 1)
1484 if (!isUnitTestRunning())
1485 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::BestQuality);
1486 else
1488 // Some tests require exact pixel values and would be confused by smooth-scaling.
1489 // And some draw something smooth and not smooth-scaling there would break the checks.
1490 if (isUnitTestRunning("BackendTest__testDrawHaflEllipseAAWithPolyLineB2D_")
1491 || isUnitTestRunning("BackendTest__testDrawRectAAWithLine_"))
1493 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::BestQuality);
1495 else
1496 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::NearestNeighbor);
1499 return bitmap;
1502 Color SkiaSalGraphicsImpl::getPixel(tools::Long nX, tools::Long nY)
1504 SkiaZone zone;
1505 checkSurface();
1506 SAL_INFO("vcl.skia.trace", "getpixel(" << this << "): " << Point(nX, nY));
1507 flushDrawing();
1508 // This is presumably slow, but getPixel() should be generally used only by unit tests.
1509 SkBitmap bitmap;
1510 if (!bitmap.tryAllocN32Pixels(mSurface->width(), mSurface->height()))
1511 abort();
1512 if (!mSurface->readPixels(bitmap, 0, 0))
1513 abort();
1514 return fromSkColor(bitmap.getColor(nX * mScaling, nY * mScaling));
1517 void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFlags)
1519 preDraw();
1520 SAL_INFO("vcl.skia.trace", "invert(" << this << "): " << rPoly << ":" << int(eFlags));
1521 assert(!mXorMode);
1522 SkPath aPath;
1523 aPath.incReserve(rPoly.count());
1524 addPolygonToPath(rPoly, aPath);
1525 aPath.setFillType(SkPathFillType::kEvenOdd);
1526 addUpdateRegion(aPath.getBounds());
1527 SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
1528 SkPaint aPaint;
1529 setBlendModeDifference(&aPaint);
1530 // TrackFrame just inverts a dashed path around the polygon
1531 if (eFlags == SalInvert::TrackFrame)
1533 // TrackFrame is not supposed to paint outside of the polygon (usually rectangle),
1534 // but wider stroke width usually results in that, so ensure the requirement
1535 // by clipping.
1536 getDrawCanvas()->clipRect(aPath.getBounds(), SkClipOp::kIntersect, false);
1537 aPaint.setStrokeWidth(2);
1538 constexpr float intervals[] = { 4.0f, 4.0f };
1539 aPaint.setStyle(SkPaint::kStroke_Style);
1540 aPaint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
1541 aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
1543 else
1545 aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
1546 aPaint.setStyle(SkPaint::kFill_Style);
1548 // N50 inverts in checker pattern
1549 if (eFlags == SalInvert::N50)
1551 // This creates 2x2 checker pattern bitmap
1552 // TODO Use createSkSurface() and cache the image
1553 SkBitmap aBitmap;
1554 aBitmap.allocN32Pixels(2, 2);
1555 const SkPMColor white = SkPreMultiplyARGB(0xFF, 0xFF, 0xFF, 0xFF);
1556 const SkPMColor black = SkPreMultiplyARGB(0xFF, 0x00, 0x00, 0x00);
1557 SkPMColor* scanline;
1558 scanline = aBitmap.getAddr32(0, 0);
1559 *scanline++ = white;
1560 *scanline++ = black;
1561 scanline = aBitmap.getAddr32(0, 1);
1562 *scanline++ = black;
1563 *scanline++ = white;
1564 aBitmap.setImmutable();
1565 // The bitmap is repeated in both directions the checker pattern is as big
1566 // as the polygon (usually rectangle)
1567 aPaint.setShader(
1568 aBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()));
1571 getDrawCanvas()->drawPath(aPath, aPaint);
1572 postDraw();
1575 void SkiaSalGraphicsImpl::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
1576 tools::Long nHeight, SalInvert eFlags)
1578 basegfx::B2DRectangle aRectangle(nX, nY, nX + nWidth, nY + nHeight);
1579 auto aRect = basegfx::utils::createPolygonFromRect(aRectangle);
1580 invert(aRect, eFlags);
1583 void SkiaSalGraphicsImpl::invert(sal_uInt32 nPoints, const Point* pPointArray, SalInvert eFlags)
1585 basegfx::B2DPolygon aPolygon;
1586 aPolygon.append(basegfx::B2DPoint(pPointArray[0].getX(), pPointArray[0].getY()), nPoints);
1587 for (sal_uInt32 i = 1; i < nPoints; ++i)
1589 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPointArray[i].getX(), pPointArray[i].getY()));
1591 aPolygon.setClosed(true);
1593 invert(aPolygon, eFlags);
1596 bool SkiaSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*,
1597 sal_uInt32)
1599 return false;
1602 // Create SkImage from a bitmap and possibly an alpha mask (the usual VCL one-minus-alpha),
1603 // with the given target size. Result will be possibly cached, unless disabled.
1604 // Especially in raster mode scaling and alpha blending may be expensive if done repeatedly.
1605 sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitmap,
1606 const SkiaSalBitmap* alphaBitmap,
1607 const Size& targetSize)
1609 sk_sp<SkImage> image;
1610 if (targetSize.IsEmpty())
1611 return image;
1612 if (alphaBitmap && alphaBitmap->IsFullyOpaqueAsAlpha())
1613 alphaBitmap = nullptr; // the alpha can be ignored
1614 if (bitmap.PreferSkShader() && (!alphaBitmap || alphaBitmap->PreferSkShader()))
1615 return image;
1617 // Probably not much point in caching of just doing a copy.
1618 if (alphaBitmap == nullptr && targetSize == bitmap.GetSize())
1619 return image;
1620 // Image too small to be worth caching if not scaling.
1621 if (targetSize == bitmap.GetSize() && targetSize.Width() < 100 && targetSize.Height() < 100)
1622 return image;
1623 // GPU-accelerated drawing with SkShader should be fast enough to not need caching.
1624 if (isGPU())
1626 // tdf#140925: But if this is such an extensive downscaling that caching the result
1627 // would noticeably reduce amount of data processed by the GPU on repeated usage, do it.
1628 int reduceRatio = bitmap.GetSize().Width() * bitmap.GetSize().Height() / targetSize.Width()
1629 / targetSize.Height();
1630 if (reduceRatio < 10)
1631 return image;
1633 // In some cases (tdf#134237) the target size may be very large. In that case it's
1634 // better to rely on Skia to clip and draw only the necessary, rather than prepare
1635 // a very large image only to not use most of it.
1636 const Size drawAreaSize = mClipRegion.GetBoundRect().GetSize() * mScaling;
1637 if (targetSize.Width() > drawAreaSize.Width() || targetSize.Height() > drawAreaSize.Height())
1639 // This is a bit tricky. The condition above just checks that at least a part of the resulting
1640 // image will not be used (it's larger then our drawing area). But this may often happen
1641 // when just scrolling a document with a large image, where the caching may very well be worth it.
1642 // Since the problem is mainly the cost of upscaling and then the size of the resulting bitmap,
1643 // compute a ratio of how much this is going to be scaled up, how much this is larger than
1644 // the drawing area, and then refuse to cache if it's too much.
1645 const double upscaleRatio
1646 = std::max(1.0, 1.0 * targetSize.Width() / bitmap.GetSize().Width()
1647 * targetSize.Height() / bitmap.GetSize().Height());
1648 const double oversizeRatio = 1.0 * targetSize.Width() / drawAreaSize.Width()
1649 * targetSize.Height() / drawAreaSize.Height();
1650 const double ratio = upscaleRatio * oversizeRatio;
1651 if (ratio > 4)
1653 SAL_INFO("vcl.skia.trace", "mergecachebitmaps("
1654 << this << "): not caching, ratio:" << ratio << ", "
1655 << bitmap.GetSize() << "->" << targetSize << " in "
1656 << drawAreaSize);
1657 return image;
1660 // Do not cache the result if it would take most of the cache and thus get evicted soon.
1661 if (targetSize.Width() * targetSize.Height() * 4 > maxImageCacheSize() * 0.7)
1662 return image;
1663 OString key = OString::number(targetSize.Width()) + "x" + OString::number(targetSize.Height())
1664 + "_" + bitmap.GetImageKey();
1665 if (alphaBitmap)
1666 key += "_" + alphaBitmap->GetAlphaImageKey();
1667 image = findCachedImage(key);
1668 if (image)
1670 assert(image->width() == targetSize.Width() && image->height() == targetSize.Height());
1671 return image;
1673 sk_sp<SkSurface> tmpSurface
1674 = createSkSurface(targetSize, alphaBitmap ? kPremul_SkAlphaType : bitmap.alphaType());
1675 if (!tmpSurface)
1676 return nullptr;
1677 SkCanvas* canvas = tmpSurface->getCanvas();
1678 SkAutoCanvasRestore autoRestore(canvas, true);
1679 SkPaint paint;
1680 SkSamplingOptions samplingOptions;
1681 if (targetSize != bitmap.GetSize())
1683 SkMatrix matrix;
1684 matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / bitmap.GetSize().Width());
1685 matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / bitmap.GetSize().Height());
1686 canvas->concat(matrix);
1687 samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix, 1);
1689 if (alphaBitmap != nullptr)
1691 canvas->clear(SK_ColorTRANSPARENT);
1692 paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut, bitmap.GetSkShader(samplingOptions),
1693 alphaBitmap->GetAlphaSkShader(samplingOptions)));
1694 canvas->drawPaint(paint);
1696 else if (bitmap.PreferSkShader())
1698 paint.setShader(bitmap.GetSkShader(samplingOptions));
1699 canvas->drawPaint(paint);
1701 else
1702 canvas->drawImage(bitmap.GetSkImage(), 0, 0, samplingOptions, &paint);
1703 if (isGPU())
1704 SAL_INFO("vcl.skia.trace", "mergecachebitmaps(" << this << "): caching GPU downscaling:"
1705 << bitmap.GetSize() << "->" << targetSize);
1706 image = makeCheckedImageSnapshot(tmpSurface);
1707 addCachedImage(key, image);
1708 return image;
1711 bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
1712 const SalBitmap& rAlphaBitmap)
1714 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1715 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1716 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1717 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1718 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1719 // alpha blending or scaling.
1720 SalTwoRect imagePosAry(rPosAry);
1721 Size imageSize = rSourceBitmap.GetSize();
1722 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1723 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1724 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1725 && rPosAry.mnSrcWidth == rSourceBitmap.GetSize().Width()
1726 && rPosAry.mnSrcHeight == rSourceBitmap.GetSize().Height())
1728 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1729 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1730 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1732 sk_sp<SkImage> image
1733 = mergeCacheBitmaps(rSkiaSourceBitmap, &rSkiaAlphaBitmap, imageSize * mScaling);
1734 if (image)
1735 drawImage(imagePosAry, image, mScaling);
1736 else if (rSkiaAlphaBitmap.IsFullyOpaqueAsAlpha()
1737 && !rSkiaSourceBitmap.PreferSkShader()) // alpha can be ignored
1738 drawBitmap(rPosAry, rSkiaSourceBitmap);
1739 else
1740 drawShader(rPosAry,
1741 SkShaders::Blend(
1742 SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1743 rSkiaSourceBitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)),
1744 rSkiaAlphaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
1745 return true;
1748 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBitmap& bitmap,
1749 SkBlendMode blendMode)
1751 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1752 // scaling.
1753 SalTwoRect imagePosAry(rPosAry);
1754 Size imageSize = bitmap.GetSize();
1755 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1756 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1757 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1758 && rPosAry.mnSrcWidth == bitmap.GetSize().Width()
1759 && rPosAry.mnSrcHeight == bitmap.GetSize().Height())
1761 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1762 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1763 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1765 sk_sp<SkImage> image = mergeCacheBitmaps(bitmap, nullptr, imageSize * mScaling);
1766 if (image)
1767 drawImage(imagePosAry, image, mScaling, blendMode);
1768 else if (bitmap.PreferSkShader())
1769 drawShader(rPosAry, bitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)), blendMode);
1770 else
1771 drawImage(rPosAry, bitmap.GetSkImage(), 1, blendMode);
1774 void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage,
1775 int srcScaling, SkBlendMode eBlendMode)
1777 SkRect aSourceRect
1778 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1779 if (srcScaling != 1)
1780 aSourceRect = scaleRect(aSourceRect, srcScaling);
1781 SkRect aDestinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY,
1782 rPosAry.mnDestWidth, rPosAry.mnDestHeight);
1784 SkPaint aPaint;
1785 aPaint.setBlendMode(eBlendMode);
1787 preDraw();
1788 SAL_INFO("vcl.skia.trace",
1789 "drawimage(" << this << "): " << rPosAry << ":" << SkBlendMode_Name(eBlendMode));
1790 addUpdateRegion(aDestinationRect);
1791 getDrawCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect,
1792 makeSamplingOptions(rPosAry, mScaling, srcScaling), &aPaint,
1793 SkCanvas::kFast_SrcRectConstraint);
1794 ++mPendingOperationsToFlush; // tdf#136369
1795 postDraw();
1798 // SkShader can be used to merge multiple bitmaps with appropriate blend modes (e.g. when
1799 // merging a bitmap with its alpha mask).
1800 void SkiaSalGraphicsImpl::drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader,
1801 SkBlendMode blendMode)
1803 preDraw();
1804 SAL_INFO("vcl.skia.trace", "drawshader(" << this << "): " << rPosAry);
1805 SkRect destinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1806 rPosAry.mnDestHeight);
1807 addUpdateRegion(destinationRect);
1808 SkPaint paint;
1809 paint.setBlendMode(blendMode);
1810 paint.setShader(shader);
1811 SkCanvas* canvas = getDrawCanvas();
1812 // Scaling needs to be done explicitly using a matrix.
1813 SkAutoCanvasRestore autoRestore(canvas, true);
1814 SkMatrix matrix = SkMatrix::Translate(rPosAry.mnDestX, rPosAry.mnDestY)
1815 * SkMatrix::Scale(1.0 * rPosAry.mnDestWidth / rPosAry.mnSrcWidth,
1816 1.0 * rPosAry.mnDestHeight / rPosAry.mnSrcHeight)
1817 * SkMatrix::Translate(-rPosAry.mnSrcX, -rPosAry.mnSrcY);
1818 #ifndef NDEBUG
1819 // Handle floating point imprecisions, round p1 to 2 decimal places.
1820 auto compareRounded = [](const SkPoint& p1, const SkPoint& p2) {
1821 return rtl::math::round(p1.x(), 2) == p2.x() && rtl::math::round(p1.y(), 2) == p2.y();
1823 #endif
1824 assert(compareRounded(matrix.mapXY(rPosAry.mnSrcX, rPosAry.mnSrcY),
1825 SkPoint::Make(rPosAry.mnDestX, rPosAry.mnDestY)));
1826 assert(compareRounded(
1827 matrix.mapXY(rPosAry.mnSrcX + rPosAry.mnSrcWidth, rPosAry.mnSrcY + rPosAry.mnSrcHeight),
1828 SkPoint::Make(rPosAry.mnDestX + rPosAry.mnDestWidth,
1829 rPosAry.mnDestY + rPosAry.mnDestHeight)));
1830 canvas->concat(matrix);
1831 SkRect sourceRect
1832 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1833 canvas->drawRect(sourceRect, paint);
1834 postDraw();
1837 bool SkiaSalGraphicsImpl::hasFastDrawTransformedBitmap() const
1839 // Return true even in raster mode, even that way Skia is faster than e.g. GraphicObject
1840 // trying to handle stuff manually.
1841 return true;
1844 // Whether applying matrix needs image smoothing for the transformation.
1845 static bool matrixNeedsHighQuality(const SkMatrix& matrix)
1847 if (matrix.isIdentity())
1848 return false;
1849 if (matrix.isScaleTranslate())
1851 if (abs(matrix.getScaleX()) == 1 && abs(matrix.getScaleY()) == 1)
1852 return false; // Only at most flipping and keeping the size.
1853 return true;
1855 assert(!matrix.hasPerspective()); // we do not use this
1856 if (matrix.getScaleX() == 0 && matrix.getScaleY() == 0)
1858 // Rotating 90 or 270 degrees while keeping the size.
1859 if ((matrix.getSkewX() == 1 && matrix.getSkewY() == -1)
1860 || (matrix.getSkewX() == -1 && matrix.getSkewY() == 1))
1861 return false;
1863 return true;
1866 namespace SkiaTests
1868 bool matrixNeedsHighQuality(const SkMatrix& matrix) { return ::matrixNeedsHighQuality(matrix); }
1871 bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
1872 const basegfx::B2DPoint& rX,
1873 const basegfx::B2DPoint& rY,
1874 const SalBitmap& rSourceBitmap,
1875 const SalBitmap* pAlphaBitmap, double fAlpha)
1877 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1878 assert(!pAlphaBitmap || dynamic_cast<const SkiaSalBitmap*>(pAlphaBitmap));
1880 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1881 const SkiaSalBitmap* pSkiaAlphaBitmap = static_cast<const SkiaSalBitmap*>(pAlphaBitmap);
1883 if (pSkiaAlphaBitmap && pSkiaAlphaBitmap->IsFullyOpaqueAsAlpha())
1884 pSkiaAlphaBitmap = nullptr; // the alpha can be ignored
1886 // Setup the image transformation,
1887 // using the rNull, rX, rY points as destinations for the (0,0), (Width,0), (0,Height) source points.
1888 const basegfx::B2DVector aXRel = rX - rNull;
1889 const basegfx::B2DVector aYRel = rY - rNull;
1891 preDraw();
1892 SAL_INFO("vcl.skia.trace", "drawtransformedbitmap(" << this << "): " << rSourceBitmap.GetSize()
1893 << " " << rNull << ":" << rX << ":" << rY);
1895 addUpdateRegion(SkRect::MakeWH(GetWidth(), GetHeight())); // can't tell, use whole area
1896 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1897 // alpha blending or scaling.
1898 // The extra fAlpha blending is not cached, with the assumption that it usually gradually changes
1899 // for each invocation.
1900 // Pass size * mScaling to mergeCacheBitmaps() so that it prepares the size that will be needed
1901 // after the mScaling-scaling matrix, but otherwise calculate everything else using the VCL coordinates.
1902 Size imageSize(round(aXRel.getLength()), round(aYRel.getLength()));
1903 sk_sp<SkImage> imageToDraw
1904 = mergeCacheBitmaps(rSkiaBitmap, pSkiaAlphaBitmap, imageSize * mScaling);
1905 if (imageToDraw)
1907 SkMatrix matrix;
1908 // Round sizes for scaling, so that sub-pixel differences don't
1909 // trigger unnecessary scaling. Image has already been scaled
1910 // by mergeCacheBitmaps() and we shouldn't scale here again
1911 // unless the drawing is also skewed.
1912 matrix.set(SkMatrix::kMScaleX, round(aXRel.getX()) / imageSize.Width());
1913 matrix.set(SkMatrix::kMScaleY, round(aYRel.getY()) / imageSize.Height());
1914 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / imageSize.Width());
1915 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / imageSize.Height());
1916 matrix.set(SkMatrix::kMTransX, rNull.getX());
1917 matrix.set(SkMatrix::kMTransY, rNull.getY());
1918 SkCanvas* canvas = getDrawCanvas();
1919 SkAutoCanvasRestore autoRestore(canvas, true);
1920 canvas->concat(matrix);
1921 SkSamplingOptions samplingOptions;
1922 // If the matrix changes geometry, we need to smooth-scale. If there's mScaling,
1923 // that's already been handled by mergeCacheBitmaps().
1924 if (matrixNeedsHighQuality(matrix))
1925 samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix, 1);
1926 if (fAlpha == 1.0)
1928 // Specify sizes to scale the image size back if needed (because of mScaling).
1929 SkRect dstRect = SkRect::MakeWH(imageSize.Width(), imageSize.Height());
1930 SkRect srcRect = SkRect::MakeWH(imageToDraw->width(), imageToDraw->height());
1931 canvas->drawImageRect(imageToDraw, srcRect, dstRect, samplingOptions, nullptr,
1932 SkCanvas::kFast_SrcRectConstraint);
1934 else
1936 SkPaint paint;
1937 // Scale the image size back if needed.
1938 SkMatrix scale = SkMatrix::Scale(1.0 / mScaling, 1.0 / mScaling);
1939 paint.setShader(SkShaders::Blend(
1940 SkBlendMode::kDstIn, imageToDraw->makeShader(samplingOptions, &scale),
1941 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
1942 canvas->drawRect(SkRect::MakeWH(imageSize.Width(), imageSize.Height()), paint);
1945 else
1947 SkMatrix matrix;
1948 const Size aSize = rSourceBitmap.GetSize();
1949 matrix.set(SkMatrix::kMScaleX, aXRel.getX() / aSize.Width());
1950 matrix.set(SkMatrix::kMScaleY, aYRel.getY() / aSize.Height());
1951 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / aSize.Width());
1952 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / aSize.Height());
1953 matrix.set(SkMatrix::kMTransX, rNull.getX());
1954 matrix.set(SkMatrix::kMTransY, rNull.getY());
1955 SkCanvas* canvas = getDrawCanvas();
1956 SkAutoCanvasRestore autoRestore(canvas, true);
1957 canvas->concat(matrix);
1958 SkSamplingOptions samplingOptions;
1959 if (matrixNeedsHighQuality(matrix) || (mScaling != 1 && !isUnitTestRunning()))
1960 samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix, mScaling);
1961 if (pSkiaAlphaBitmap)
1963 SkPaint paint;
1964 paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1965 rSkiaBitmap.GetSkShader(samplingOptions),
1966 pSkiaAlphaBitmap->GetAlphaSkShader(samplingOptions)));
1967 if (fAlpha != 1.0)
1968 paint.setShader(
1969 SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
1970 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
1971 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
1973 else if (rSkiaBitmap.PreferSkShader() || fAlpha != 1.0)
1975 SkPaint paint;
1976 paint.setShader(rSkiaBitmap.GetSkShader(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
1985 canvas->drawImage(rSkiaBitmap.GetSkImage(), 0, 0, samplingOptions);
1988 postDraw();
1989 return true;
1992 bool SkiaSalGraphicsImpl::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
1993 tools::Long nHeight, sal_uInt8 nTransparency)
1995 privateDrawAlphaRect(nX, nY, nWidth, nHeight, nTransparency / 100.0);
1996 return true;
1999 bool SkiaSalGraphicsImpl::drawGradient(const tools::PolyPolygon& rPolyPolygon,
2000 const Gradient& rGradient)
2002 if (rGradient.GetStyle() != GradientStyle::Linear
2003 && rGradient.GetStyle() != GradientStyle::Axial
2004 && rGradient.GetStyle() != GradientStyle::Radial)
2005 return false; // unsupported
2006 if (rGradient.GetSteps() != 0)
2007 return false; // We can't tell Skia how many colors to use in the gradient.
2008 preDraw();
2009 SAL_INFO("vcl.skia.trace", "drawgradient(" << this << "): " << rPolyPolygon.getB2DPolyPolygon()
2010 << ":" << static_cast<int>(rGradient.GetStyle()));
2011 tools::Rectangle boundRect(rPolyPolygon.GetBoundRect());
2012 if (boundRect.IsEmpty())
2013 return true;
2014 SkPath path;
2015 if (rPolyPolygon.IsRect())
2017 // Rect->Polygon conversion loses the right and bottom edge, fix that.
2018 path.addRect(SkRect::MakeXYWH(boundRect.getX(), boundRect.getY(), boundRect.GetWidth(),
2019 boundRect.GetHeight()));
2020 boundRect.AdjustRight(1);
2021 boundRect.AdjustBottom(1);
2023 else
2024 addPolyPolygonToPath(rPolyPolygon.getB2DPolyPolygon(), path);
2025 path.setFillType(SkPathFillType::kEvenOdd);
2026 addUpdateRegion(path.getBounds());
2028 Gradient aGradient(rGradient);
2029 tools::Rectangle aBoundRect;
2030 Point aCenter;
2031 aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10);
2032 aGradient.GetBoundRect(boundRect, aBoundRect, aCenter);
2034 SkColor startColor
2035 = toSkColorWithIntensity(rGradient.GetStartColor(), rGradient.GetStartIntensity());
2036 SkColor endColor = toSkColorWithIntensity(rGradient.GetEndColor(), rGradient.GetEndIntensity());
2038 sk_sp<SkShader> shader;
2039 if (rGradient.GetStyle() == GradientStyle::Linear)
2041 tools::Polygon aPoly(aBoundRect);
2042 aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
2043 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
2044 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
2045 SkColor colors[2] = { startColor, endColor };
2046 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
2047 shader = SkGradientShader::MakeLinear(points, colors, pos, 2, SkTileMode::kClamp);
2049 else if (rGradient.GetStyle() == GradientStyle::Axial)
2051 tools::Polygon aPoly(aBoundRect);
2052 aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
2053 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
2054 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
2055 SkColor colors[3] = { endColor, startColor, endColor };
2056 SkScalar border = SkDoubleToScalar(aGradient.GetBorder() / 100.0);
2057 SkScalar pos[3]
2058 = { std::min<SkScalar>(border, 0.5), 0.5, std::max<SkScalar>(1 - border, 0.5) };
2059 shader = SkGradientShader::MakeLinear(points, colors, pos, 3, SkTileMode::kClamp);
2061 else
2063 // Move the center by (-1,-1) (the default VCL algorithm is a bit off-center that way,
2064 // Skia is the opposite way).
2065 SkPoint center = SkPoint::Make(toSkX(aCenter.X()) - 1, toSkY(aCenter.Y()) - 1);
2066 SkScalar radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
2067 SkColor colors[2] = { endColor, startColor };
2068 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
2069 shader = SkGradientShader::MakeRadial(center, radius, colors, pos, 2, SkTileMode::kClamp);
2072 SkPaint paint;
2073 paint.setAntiAlias(mParent.getAntiAlias());
2074 paint.setShader(shader);
2075 getDrawCanvas()->drawPath(path, paint);
2076 postDraw();
2077 return true;
2080 bool SkiaSalGraphicsImpl::implDrawGradient(const basegfx::B2DPolyPolygon& rPolyPolygon,
2081 const SalGradient& rGradient)
2083 preDraw();
2084 SAL_INFO("vcl.skia.trace",
2085 "impldrawgradient(" << this << "): " << rPolyPolygon << ":" << rGradient.maPoint1
2086 << "->" << rGradient.maPoint2 << ":" << rGradient.maStops.size());
2088 SkPath path;
2089 addPolyPolygonToPath(rPolyPolygon, path);
2090 path.setFillType(SkPathFillType::kEvenOdd);
2091 addUpdateRegion(path.getBounds());
2093 SkPoint points[2]
2094 = { SkPoint::Make(toSkX(rGradient.maPoint1.getX()), toSkY(rGradient.maPoint1.getY())),
2095 SkPoint::Make(toSkX(rGradient.maPoint2.getX()), toSkY(rGradient.maPoint2.getY())) };
2096 std::vector<SkColor> colors;
2097 std::vector<SkScalar> pos;
2098 for (const SalGradientStop& stop : rGradient.maStops)
2100 colors.emplace_back(toSkColor(stop.maColor));
2101 pos.emplace_back(stop.mfOffset);
2103 sk_sp<SkShader> shader = SkGradientShader::MakeLinear(points, colors.data(), pos.data(),
2104 colors.size(), SkTileMode::kDecal);
2105 SkPaint paint;
2106 paint.setAntiAlias(mParent.getAntiAlias());
2107 paint.setShader(shader);
2108 getDrawCanvas()->drawPath(path, paint);
2109 postDraw();
2110 return true;
2113 static double toRadian(Degree10 degree10th) { return (3600 - degree10th.get()) * M_PI / 1800.0; }
2114 static double toCos(Degree10 degree10th) { return SkScalarCos(toRadian(degree10th)); }
2115 static double toSin(Degree10 degree10th) { return SkScalarSin(toRadian(degree10th)); }
2117 void SkiaSalGraphicsImpl::drawGenericLayout(const GenericSalLayout& layout, Color textColor,
2118 const SkFont& font, const SkFont& verticalFont)
2120 SkiaZone zone;
2121 std::vector<SkGlyphID> glyphIds;
2122 std::vector<SkRSXform> glyphForms;
2123 std::vector<bool> verticals;
2124 glyphIds.reserve(256);
2125 glyphForms.reserve(256);
2126 verticals.reserve(256);
2127 Point aPos;
2128 const GlyphItem* pGlyph;
2129 int nStart = 0;
2130 while (layout.GetNextGlyph(&pGlyph, aPos, nStart))
2132 glyphIds.push_back(pGlyph->glyphId());
2133 Degree10 angle = layout.GetOrientation();
2134 if (pGlyph->IsVertical())
2135 angle += 900_deg10;
2136 SkRSXform form = SkRSXform::Make(toCos(angle), toSin(angle), aPos.X(), aPos.Y());
2137 glyphForms.emplace_back(std::move(form));
2138 verticals.emplace_back(pGlyph->IsVertical());
2140 if (glyphIds.empty())
2141 return;
2143 preDraw();
2144 auto getBoundRect = [&layout]() {
2145 tools::Rectangle rect;
2146 layout.GetBoundRect(rect);
2147 return rect;
2149 SAL_INFO("vcl.skia.trace", "drawtextblob(" << this << "): " << getBoundRect() << ", "
2150 << glyphIds.size() << " glyphs, " << textColor);
2152 // Vertical glyphs need a different font, so split drawing into runs that each
2153 // draw only consecutive horizontal or vertical glyphs.
2154 std::vector<bool>::const_iterator pos = verticals.cbegin();
2155 std::vector<bool>::const_iterator end = verticals.cend();
2156 while (pos != end)
2158 bool verticalRun = *pos;
2159 std::vector<bool>::const_iterator rangeEnd = std::find(pos + 1, end, !verticalRun);
2160 size_t index = pos - verticals.cbegin();
2161 size_t count = rangeEnd - pos;
2162 sk_sp<SkTextBlob> textBlob = SkTextBlob::MakeFromRSXform(
2163 glyphIds.data() + index, count * sizeof(SkGlyphID), glyphForms.data() + index,
2164 verticalRun ? verticalFont : font, SkTextEncoding::kGlyphID);
2165 addUpdateRegion(textBlob->bounds());
2166 SkPaint paint;
2167 paint.setColor(toSkColor(textColor));
2168 getDrawCanvas()->drawTextBlob(textBlob, 0, 0, paint);
2169 pos = rangeEnd;
2171 postDraw();
2174 bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
2176 switch (eType)
2178 case OutDevSupportType::B2DDraw:
2179 case OutDevSupportType::TransparentRect:
2180 return true;
2181 default:
2182 return false;
2186 static int getScaling()
2188 // It makes sense to support the debugging flag on all platforms
2189 // for unittests purpose, even if the actual windows cannot do it.
2190 if (const char* env = getenv("SAL_FORCE_HIDPI_SCALING"))
2191 return atoi(env);
2192 return 1;
2195 int SkiaSalGraphicsImpl::getWindowScaling() const
2197 static const int scaling = getScaling();
2198 return scaling;
2201 #ifdef DBG_UTIL
2202 void SkiaSalGraphicsImpl::dump(const char* file) const
2204 assert(mSurface.get());
2205 SkiaHelper::dump(mSurface, file);
2207 #endif
2209 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */