sw: document SwFormatVertOrient
[LibreOffice.git] / vcl / skia / gdiimpl.cxx
blob896849baefa3c94f771f9e721c8649561fcbf33c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <skia/gdiimpl.hxx>
22 #include <salgdi.hxx>
23 #include <skia/salbmp.hxx>
24 #include <vcl/idle.hxx>
25 #include <vcl/svapp.hxx>
26 #include <vcl/lazydelete.hxx>
27 #include <vcl/gradient.hxx>
28 #include <vcl/skia/SkiaHelper.hxx>
29 #include <skia/utils.hxx>
30 #include <skia/zone.hxx>
32 #include <SkBitmap.h>
33 #include <SkCanvas.h>
34 #include <SkGradientShader.h>
35 #include <SkPath.h>
36 #include <SkRegion.h>
37 #include <SkDashPathEffect.h>
38 #include <GrBackendSurface.h>
39 #include <SkTextBlob.h>
40 #include <SkRSXform.h>
42 #include <numeric>
43 #include <basegfx/polygon/b2dpolygontools.hxx>
44 #include <basegfx/polygon/b2dpolypolygontools.hxx>
45 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
46 #include <o3tl/sorted_vector.hxx>
47 #include <rtl/math.hxx>
49 using namespace SkiaHelper;
51 namespace
53 // Create Skia Path from B2DPolygon
54 // Note that polygons generally have the complication that when used
55 // for area (fill) operations they usually miss the right-most and
56 // bottom-most line of pixels of the bounding rectangle (see
57 // https://lists.freedesktop.org/archives/libreoffice/2019-November/083709.html).
58 // So be careful with rectangle->polygon conversions (generally avoid them).
59 void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath, sal_uInt32 nFirstIndex,
60 sal_uInt32 nLastIndex, const sal_uInt32 nPointCount, const bool bClosePath,
61 const bool bHasCurves, bool* hasOnlyOrthogonal = nullptr)
63 assert(nFirstIndex < nPointCount);
64 assert(nLastIndex <= nPointCount);
66 if (nPointCount <= 1)
67 return;
69 bool bFirst = true;
70 sal_uInt32 nPreviousIndex = nFirstIndex == 0 ? nPointCount - 1 : nFirstIndex - 1;
71 basegfx::B2DPoint aPreviousPoint = rPolygon.getB2DPoint(nPreviousIndex);
73 for (sal_uInt32 nIndex = nFirstIndex; nIndex <= nLastIndex; nIndex++)
75 if (nIndex == nPointCount && !bClosePath)
76 continue;
78 // Make sure we loop the last point to first point
79 sal_uInt32 nCurrentIndex = nIndex % nPointCount;
80 basegfx::B2DPoint aCurrentPoint = rPolygon.getB2DPoint(nCurrentIndex);
82 if (bFirst)
84 rPath.moveTo(aCurrentPoint.getX(), aCurrentPoint.getY());
85 bFirst = false;
87 else if (!bHasCurves)
89 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY());
90 // If asked for, check whether the polygon has a line that is not
91 // strictly horizontal or vertical.
92 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
93 && aCurrentPoint.getY() != aPreviousPoint.getY())
94 *hasOnlyOrthogonal = false;
96 else
98 basegfx::B2DPoint aPreviousControlPoint = rPolygon.getNextControlPoint(nPreviousIndex);
99 basegfx::B2DPoint aCurrentControlPoint = rPolygon.getPrevControlPoint(nCurrentIndex);
101 if (aPreviousControlPoint.equal(aPreviousPoint)
102 && aCurrentControlPoint.equal(aCurrentPoint))
104 rPath.lineTo(aCurrentPoint.getX(), aCurrentPoint.getY()); // a straight line
105 if (hasOnlyOrthogonal != nullptr && aCurrentPoint.getX() != aPreviousPoint.getX()
106 && aCurrentPoint.getY() != aPreviousPoint.getY())
107 *hasOnlyOrthogonal = false;
109 else
111 if (aPreviousControlPoint.equal(aPreviousPoint))
113 aPreviousControlPoint
114 = aPreviousPoint + ((aPreviousControlPoint - aCurrentPoint) * 0.0005);
116 if (aCurrentControlPoint.equal(aCurrentPoint))
118 aCurrentControlPoint
119 = aCurrentPoint + ((aCurrentControlPoint - aPreviousPoint) * 0.0005);
121 rPath.cubicTo(aPreviousControlPoint.getX(), aPreviousControlPoint.getY(),
122 aCurrentControlPoint.getX(), aCurrentControlPoint.getY(),
123 aCurrentPoint.getX(), aCurrentPoint.getY());
124 if (hasOnlyOrthogonal != nullptr)
125 *hasOnlyOrthogonal = false;
128 aPreviousPoint = aCurrentPoint;
129 nPreviousIndex = nCurrentIndex;
131 if (bClosePath && nFirstIndex == 0 && nLastIndex == nPointCount)
133 rPath.close();
137 void addPolygonToPath(const basegfx::B2DPolygon& rPolygon, SkPath& rPath,
138 bool* hasOnlyOrthogonal = nullptr)
140 addPolygonToPath(rPolygon, rPath, 0, rPolygon.count(), rPolygon.count(), rPolygon.isClosed(),
141 rPolygon.areControlPointsUsed(), hasOnlyOrthogonal);
144 void addPolyPolygonToPath(const basegfx::B2DPolyPolygon& rPolyPolygon, SkPath& rPath,
145 bool* hasOnlyOrthogonal = nullptr)
147 const sal_uInt32 nPolygonCount(rPolyPolygon.count());
149 if (nPolygonCount == 0)
150 return;
152 sal_uInt32 nPointCount = 0;
153 for (const auto& rPolygon : rPolyPolygon)
154 nPointCount += rPolygon.count() * 3; // because cubicTo is 3 elements
155 rPath.incReserve(nPointCount);
157 for (const auto& rPolygon : rPolyPolygon)
159 addPolygonToPath(rPolygon, rPath, hasOnlyOrthogonal);
163 // Check if the given polygon contains a straight line. If not, it consists
164 // solely of curves.
165 bool polygonContainsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
167 if (!rPolyPolygon.areControlPointsUsed())
168 return true; // no curves at all
169 for (const auto& rPolygon : rPolyPolygon)
171 const sal_uInt32 nPointCount(rPolygon.count());
172 bool bFirst = true;
174 const bool bClosePath(rPolygon.isClosed());
176 sal_uInt32 nCurrentIndex = 0;
177 sal_uInt32 nPreviousIndex = nPointCount - 1;
179 basegfx::B2DPoint aCurrentPoint;
180 basegfx::B2DPoint aPreviousPoint;
182 for (sal_uInt32 nIndex = 0; nIndex <= nPointCount; nIndex++)
184 if (nIndex == nPointCount && !bClosePath)
185 continue;
187 // Make sure we loop the last point to first point
188 nCurrentIndex = nIndex % nPointCount;
189 if (bFirst)
190 bFirst = false;
191 else
193 basegfx::B2DPoint aPreviousControlPoint
194 = rPolygon.getNextControlPoint(nPreviousIndex);
195 basegfx::B2DPoint aCurrentControlPoint
196 = rPolygon.getPrevControlPoint(nCurrentIndex);
198 if (aPreviousControlPoint.equal(aPreviousPoint)
199 && aCurrentControlPoint.equal(aCurrentPoint))
201 return true; // found a straight line
204 aPreviousPoint = aCurrentPoint;
205 nPreviousIndex = nCurrentIndex;
208 return false; // no straight line found
211 // returns true if the source or destination rectangles are invalid
212 bool checkInvalidSourceOrDestination(SalTwoRect const& rPosAry)
214 return rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
215 || rPosAry.mnDestHeight <= 0;
218 } // end anonymous namespace
220 // Class that triggers flushing the backing buffer when idle.
221 class SkiaFlushIdle : public Idle
223 SkiaSalGraphicsImpl* mpGraphics;
224 #ifndef NDEBUG
225 char* debugname;
226 #endif
228 public:
229 explicit SkiaFlushIdle(SkiaSalGraphicsImpl* pGraphics)
230 : Idle(get_debug_name(pGraphics))
231 , mpGraphics(pGraphics)
233 // We don't want to be swapping before we've painted.
234 SetPriority(TaskPriority::POST_PAINT);
236 #ifndef NDEBUG
237 virtual ~SkiaFlushIdle() { free(debugname); }
238 #endif
239 const char* get_debug_name(SkiaSalGraphicsImpl* pGraphics)
241 #ifndef NDEBUG
242 // Idle keeps just a pointer, so we need to store the string
243 debugname = strdup(
244 OString("skia idle 0x" + OString::number(reinterpret_cast<sal_uIntPtr>(pGraphics), 16))
245 .getStr());
246 return debugname;
247 #else
248 (void)pGraphics;
249 return "skia idle";
250 #endif
253 virtual void Invoke() override
255 mpGraphics->performFlush();
256 Stop();
257 SetPriority(TaskPriority::HIGHEST);
261 SkiaSalGraphicsImpl::SkiaSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvider* pProvider)
262 : mParent(rParent)
263 , mProvider(pProvider)
264 , mIsGPU(false)
265 , mLineColor(SALCOLOR_NONE)
266 , mFillColor(SALCOLOR_NONE)
267 , mXorMode(XorMode::None)
268 , mFlush(new SkiaFlushIdle(this))
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 (forceRaster ? RenderRaster : 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 mSurface.reset();
366 mWindowContext.reset();
367 mIsGPU = false;
368 mScaling = 1;
371 void SkiaSalGraphicsImpl::performFlush()
373 SkiaZone zone;
374 flushDrawing();
375 if (mSurface)
377 if (mDirtyRect.intersect(SkIRect::MakeWH(GetWidth(), GetHeight())))
378 flushSurfaceToWindowContext();
379 mDirtyRect.setEmpty();
383 void SkiaSalGraphicsImpl::flushSurfaceToWindowContext()
385 sk_sp<SkSurface> screenSurface = mWindowContext->getBackbufferSurface();
386 if (screenSurface != mSurface)
388 // GPU-based window contexts require calling getBackbufferSurface()
389 // for every swapBuffers(), for this reason mSurface is an offscreen surface
390 // where we keep the contents (LO does not do full redraws).
391 // So here blit the surface to the window context surface and then swap it.
392 assert(isGPU()); // Raster should always draw directly to backbuffer to save copying
393 SkPaint paint;
394 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
395 // We ignore mDirtyRect here, and mSurface already is in screenSurface coordinates,
396 // so no transformation needed.
397 screenSurface->getCanvas()->drawImage(makeCheckedImageSnapshot(mSurface), 0, 0,
398 SkSamplingOptions(), &paint);
399 screenSurface->flushAndSubmit(); // Otherwise the window is not drawn sometimes.
400 mWindowContext->swapBuffers(nullptr); // Must swap the entire surface.
402 else
404 // For raster mode use directly the backbuffer surface, it's just a bitmap
405 // surface anyway, and for those there's no real requirement to call
406 // getBackbufferSurface() repeatedly. Using our own surface would duplicate
407 // memory and cost time copying pixels around.
408 assert(!isGPU());
409 SkIRect dirtyRect = mDirtyRect;
410 if (mScaling != 1) // Adjust to mSurface coordinates if needed.
411 dirtyRect = scaleRect(dirtyRect, mScaling);
412 mWindowContext->swapBuffers(&dirtyRect);
416 void SkiaSalGraphicsImpl::DeInit() { destroySurface(); }
418 void SkiaSalGraphicsImpl::preDraw()
420 assert(comphelper::SolarMutex::get()->IsCurrentThread());
421 SkiaZone::enter(); // matched in postDraw()
422 checkSurface();
423 checkPendingDrawing();
426 void SkiaSalGraphicsImpl::postDraw()
428 scheduleFlush();
429 // Skia (at least when using Vulkan) queues drawing commands and executes them only later.
430 // But tdf#136369 leads to creating and queueing many tiny bitmaps, which makes
431 // Skia slow, and may make it even run out of memory. So force a flush if such
432 // a problematic operation has been performed too many times without a flush.
433 // Note that the counter is a static variable, as all drawing shares the same Skia drawing
434 // context (and so the flush here will also flush all drawing).
435 if (pendingOperationsToFlush > 1000)
437 mSurface->flushAndSubmit();
438 pendingOperationsToFlush = 0;
440 SkiaZone::leave(); // matched in preDraw()
441 // If there's a problem with the GPU context, abort.
442 if (GrDirectContext* context = GrAsDirectContext(mSurface->getCanvas()->recordingContext()))
444 // Running out of memory on the GPU technically could be possibly recoverable,
445 // but we don't know the exact status of the surface (and what has or has not been drawn to it),
446 // so in practice this is unrecoverable without possible data loss.
447 if (context->oomed())
449 SAL_WARN("vcl.skia", "GPU context has run out of memory, aborting.");
450 abort();
452 // Unrecoverable problem.
453 if (context->abandoned())
455 SAL_WARN("vcl.skia", "GPU context has been abandoned, aborting.");
456 abort();
461 void SkiaSalGraphicsImpl::scheduleFlush()
463 if (!isOffscreen())
465 if (!Application::IsInExecute())
466 performFlush(); // otherwise nothing would trigger idle rendering
467 else if (!mFlush->IsActive())
468 mFlush->Start();
472 // VCL can sometimes resize us without telling us, update the surface if needed.
473 // Also create the surface on demand if it has not been created yet (it is a waste
474 // to create it in Init() if it gets recreated later anyway).
475 void SkiaSalGraphicsImpl::checkSurface()
477 if (!mSurface)
479 createSurface();
480 SAL_INFO("vcl.skia.trace",
481 "create(" << this << "): " << Size(mSurface->width(), mSurface->height()));
483 else if (GetWidth() * mScaling != mSurface->width()
484 || GetHeight() * mScaling != mSurface->height())
486 if (!avoidRecreateByResize())
488 Size oldSize(mSurface->width(), mSurface->height());
489 // Recreating a surface means that the old SkSurface contents will be lost.
490 // But if a window has been resized the windowing system may send repaint events
491 // only for changed parts and VCL would not repaint the whole area, assuming
492 // that some parts have not changed (this is what seems to cause tdf#131952).
493 // So carry over the old contents for windows, even though generally everything
494 // will be usually repainted anyway.
495 sk_sp<SkImage> snapshot;
496 if (!isOffscreen())
498 flushDrawing();
499 snapshot = makeCheckedImageSnapshot(mSurface);
502 destroySurface();
503 createSurface();
505 if (snapshot)
507 SkPaint paint;
508 paint.setBlendMode(SkBlendMode::kSrc); // copy as is
509 // Scaling by current mScaling is active, undo that. We assume that the scaling
510 // does not change.
511 resetCanvasScalingAndClipping();
512 mSurface->getCanvas()->drawImage(snapshot, 0, 0, SkSamplingOptions(), &paint);
513 setCanvasScalingAndClipping();
515 SAL_INFO("vcl.skia.trace", "recreate(" << this << "): old " << oldSize << " new "
516 << Size(mSurface->width(), mSurface->height())
517 << " requested "
518 << Size(GetWidth(), GetHeight()));
523 bool SkiaSalGraphicsImpl::avoidRecreateByResize() const
525 // Keep the old surface if VCL sends us a broken size (see isOffscreen()).
526 if (GetWidth() == 0 || GetHeight() == 0)
527 return true;
528 return false;
531 void SkiaSalGraphicsImpl::flushDrawing()
533 if (!mSurface)
534 return;
535 checkPendingDrawing();
536 ++pendingOperationsToFlush;
539 void SkiaSalGraphicsImpl::setCanvasScalingAndClipping()
541 SkCanvas* canvas = mSurface->getCanvas();
542 assert(canvas->getSaveCount() == 1);
543 // If HiDPI scaling is active, simply set a scaling matrix for the canvas. This means
544 // that all painting can use VCL coordinates and they'll be automatically translated to mSurface
545 // scaled coordinates. If that is not wanted, the scale() state needs to be temporarily unset.
546 // State such as mDirtyRect is not scaled, the scaling matrix applies to clipping too,
547 // and the rest needs to be handled explicitly.
548 // When reading mSurface contents there's no automatic scaling and it needs to be handled explicitly.
549 canvas->save(); // keep the original state without any scaling
550 canvas->scale(mScaling, mScaling);
552 // SkCanvas::clipRegion() can only further reduce the clip region,
553 // but we need to set the given region, which may extend it.
554 // So handle that by always having the full clip region saved on the stack
555 // and always go back to that. SkCanvas::restore() only affects the clip
556 // and the matrix.
557 canvas->save(); // keep scaled state without clipping
558 setCanvasClipRegion(canvas, mClipRegion);
561 void SkiaSalGraphicsImpl::resetCanvasScalingAndClipping()
563 SkCanvas* canvas = mSurface->getCanvas();
564 assert(canvas->getSaveCount() == 3);
565 canvas->restore(); // undo clipping
566 canvas->restore(); // undo scaling
569 bool SkiaSalGraphicsImpl::setClipRegion(const vcl::Region& region)
571 if (mClipRegion == region)
572 return true;
573 SkiaZone zone;
574 checkPendingDrawing();
575 checkSurface();
576 mClipRegion = region;
577 SAL_INFO("vcl.skia.trace", "setclipregion(" << this << "): " << region);
578 SkCanvas* canvas = mSurface->getCanvas();
579 assert(canvas->getSaveCount() == 3);
580 canvas->restore(); // undo previous clip state, see setCanvasScalingAndClipping()
581 canvas->save();
582 setCanvasClipRegion(canvas, region);
583 return true;
586 void SkiaSalGraphicsImpl::setCanvasClipRegion(SkCanvas* canvas, const vcl::Region& region)
588 SkiaZone zone;
589 SkPath path;
590 // Always use region rectangles, regardless of what the region uses internally.
591 // That's what other VCL backends do, and trying to use addPolyPolygonToPath()
592 // in case a polygon is used leads to off-by-one errors such as tdf#133208.
593 RectangleVector rectangles;
594 region.GetRegionRectangles(rectangles);
595 path.incReserve(rectangles.size() + 1);
596 for (const tools::Rectangle& rectangle : rectangles)
597 path.addRect(SkRect::MakeXYWH(rectangle.getX(), rectangle.getY(), rectangle.GetWidth(),
598 rectangle.GetHeight()));
599 path.setFillType(SkPathFillType::kEvenOdd);
600 canvas->clipPath(path);
603 void SkiaSalGraphicsImpl::ResetClipRegion()
605 setClipRegion(vcl::Region(tools::Rectangle(0, 0, GetWidth(), GetHeight())));
608 const vcl::Region& SkiaSalGraphicsImpl::getClipRegion() const { return mClipRegion; }
610 sal_uInt16 SkiaSalGraphicsImpl::GetBitCount() const { return 32; }
612 tools::Long SkiaSalGraphicsImpl::GetGraphicsWidth() const { return GetWidth(); }
614 void SkiaSalGraphicsImpl::SetLineColor()
616 checkPendingDrawing();
617 mLineColor = SALCOLOR_NONE;
620 void SkiaSalGraphicsImpl::SetLineColor(Color nColor)
622 checkPendingDrawing();
623 mLineColor = nColor;
626 void SkiaSalGraphicsImpl::SetFillColor()
628 checkPendingDrawing();
629 mFillColor = SALCOLOR_NONE;
632 void SkiaSalGraphicsImpl::SetFillColor(Color nColor)
634 checkPendingDrawing();
635 mFillColor = nColor;
638 void SkiaSalGraphicsImpl::SetXORMode(bool set, bool invert)
640 XorMode newMode = set ? (invert ? XorMode::Invert : XorMode::Xor) : XorMode::None;
641 if (newMode == mXorMode)
642 return;
643 checkPendingDrawing();
644 SAL_INFO("vcl.skia.trace", "setxormode(" << this << "): " << set << "/" << invert);
645 mXorMode = newMode;
648 void SkiaSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor)
650 checkPendingDrawing();
651 switch (nROPColor)
653 case SalROPColor::N0:
654 mLineColor = Color(0, 0, 0);
655 break;
656 case SalROPColor::N1:
657 mLineColor = Color(0xff, 0xff, 0xff);
658 break;
659 case SalROPColor::Invert:
660 mLineColor = Color(0xff, 0xff, 0xff);
661 break;
665 void SkiaSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor)
667 checkPendingDrawing();
668 switch (nROPColor)
670 case SalROPColor::N0:
671 mFillColor = Color(0, 0, 0);
672 break;
673 case SalROPColor::N1:
674 mFillColor = Color(0xff, 0xff, 0xff);
675 break;
676 case SalROPColor::Invert:
677 mFillColor = Color(0xff, 0xff, 0xff);
678 break;
682 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY)
684 drawPixel(nX, nY, mLineColor);
687 void SkiaSalGraphicsImpl::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
689 if (nColor == SALCOLOR_NONE)
690 return;
691 preDraw();
692 SAL_INFO("vcl.skia.trace", "drawpixel(" << this << "): " << Point(nX, nY) << ":" << nColor);
693 addUpdateRegion(SkRect::MakeXYWH(nX, nY, 1, 1));
694 SkPaint paint = makePixelPaint(nColor);
695 // Apparently drawPixel() is actually expected to set the pixel and not draw it.
696 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
697 if (mScaling != 1 && isUnitTestRunning())
699 // On HiDPI displays, draw a square on the entire non-hidpi "pixel" when running unittests,
700 // since tests often require precise pixel drawing.
701 paint.setStrokeWidth(1); // this will be scaled by mScaling
702 paint.setStrokeCap(SkPaint::kSquare_Cap);
704 getDrawCanvas()->drawPoint(toSkX(nX), toSkY(nY), paint);
705 postDraw();
708 void SkiaSalGraphicsImpl::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
709 tools::Long nY2)
711 if (mLineColor == SALCOLOR_NONE)
712 return;
713 preDraw();
714 SAL_INFO("vcl.skia.trace", "drawline(" << this << "): " << Point(nX1, nY1) << "->"
715 << Point(nX2, nY2) << ":" << mLineColor);
716 addUpdateRegion(SkRect::MakeLTRB(nX1, nY1, nX2, nY2).makeSorted());
717 SkPaint paint = makeLinePaint();
718 paint.setAntiAlias(mParent.getAntiAlias());
719 if (mScaling != 1 && isUnitTestRunning())
721 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
722 // smoothing that would confuse unittests.
723 paint.setStrokeWidth(1); // this will be scaled by mScaling
724 paint.setStrokeCap(SkPaint::kSquare_Cap);
726 getDrawCanvas()->drawLine(toSkX(nX1), toSkY(nY1), toSkX(nX2), toSkY(nY2), paint);
727 postDraw();
730 void SkiaSalGraphicsImpl::privateDrawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
731 tools::Long nHeight, double fTransparency,
732 bool blockAA)
734 preDraw();
735 SAL_INFO("vcl.skia.trace",
736 "privatedrawrect(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight)
737 << ":" << mLineColor << ":" << mFillColor << ":" << fTransparency);
738 addUpdateRegion(SkRect::MakeXYWH(nX, nY, nWidth, nHeight));
739 SkCanvas* canvas = getDrawCanvas();
740 if (mFillColor != SALCOLOR_NONE)
742 SkPaint paint = makeFillPaint(fTransparency);
743 paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
744 // HACK: If the polygon is just a line, it still should be drawn. But when filling
745 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
746 if (mLineColor == SALCOLOR_NONE && SkSize::Make(nWidth, nHeight).isEmpty())
747 paint.setStyle(SkPaint::kStroke_Style);
748 canvas->drawIRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), paint);
750 if (mLineColor != SALCOLOR_NONE && mLineColor != mFillColor) // otherwise handled by fill
752 SkPaint paint = makeLinePaint(fTransparency);
753 paint.setAntiAlias(!blockAA && mParent.getAntiAlias());
754 if (mScaling != 1 && isUnitTestRunning())
756 // On HiDPI displays, do not draw just a hairline but instead a full-width "pixel" when running unittests,
757 // since tests often require precise pixel drawing.
758 paint.setStrokeWidth(1); // this will be scaled by mScaling
759 paint.setStrokeCap(SkPaint::kSquare_Cap);
761 // The obnoxious "-1 DrawRect()" hack that I don't understand the purpose of (and I'm not sure
762 // if anybody does), but without it some cases do not work. The max() is needed because Skia
763 // will not draw anything if width or height is 0.
764 canvas->drawRect(SkRect::MakeXYWH(toSkX(nX), toSkY(nY),
765 std::max(tools::Long(1), nWidth - 1),
766 std::max(tools::Long(1), nHeight - 1)),
767 paint);
769 postDraw();
772 void SkiaSalGraphicsImpl::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
773 tools::Long nHeight)
775 privateDrawAlphaRect(nX, nY, nWidth, nHeight, 0.0, true);
778 void SkiaSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
780 basegfx::B2DPolygon aPolygon;
781 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
782 for (sal_uInt32 i = 1; i < nPoints; ++i)
783 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
784 aPolygon.setClosed(false);
786 drawPolyLine(basegfx::B2DHomMatrix(), aPolygon, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
787 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false);
790 void SkiaSalGraphicsImpl::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
792 basegfx::B2DPolygon aPolygon;
793 aPolygon.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
794 for (sal_uInt32 i = 1; i < nPoints; ++i)
795 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
797 drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPolygon), 0.0);
800 void SkiaSalGraphicsImpl::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
801 const Point** pPtAry)
803 basegfx::B2DPolyPolygon aPolyPolygon;
804 for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
806 sal_uInt32 nPoints = pPoints[nPolygon];
807 if (nPoints)
809 const Point* pSubPoints = pPtAry[nPolygon];
810 basegfx::B2DPolygon aPolygon;
811 aPolygon.append(basegfx::B2DPoint(pSubPoints->getX(), pSubPoints->getY()), nPoints);
812 for (sal_uInt32 i = 1; i < nPoints; ++i)
813 aPolygon.setB2DPoint(i,
814 basegfx::B2DPoint(pSubPoints[i].getX(), pSubPoints[i].getY()));
816 aPolyPolygon.append(aPolygon);
820 drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPolygon, 0.0);
823 bool SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
824 const basegfx::B2DPolyPolygon& rPolyPolygon,
825 double fTransparency)
827 const bool bHasFill(mFillColor != SALCOLOR_NONE);
828 const bool bHasLine(mLineColor != SALCOLOR_NONE);
830 if (rPolyPolygon.count() == 0 || !(bHasFill || bHasLine) || fTransparency < 0.0
831 || fTransparency >= 1.0)
832 return true;
834 basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
835 aPolyPolygon.transform(rObjectToDevice);
837 SAL_INFO("vcl.skia.trace", "drawpolypolygon(" << this << "): " << aPolyPolygon << ":"
838 << mLineColor << ":" << mFillColor);
840 if (delayDrawPolyPolygon(aPolyPolygon, fTransparency))
842 scheduleFlush();
843 return true;
846 performDrawPolyPolygon(aPolyPolygon, fTransparency, mParent.getAntiAlias());
847 return true;
850 void SkiaSalGraphicsImpl::performDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
851 double fTransparency, bool useAA)
853 preDraw();
855 SkPath polygonPath;
856 bool hasOnlyOrthogonal = true;
857 addPolyPolygonToPath(aPolyPolygon, polygonPath, &hasOnlyOrthogonal);
858 polygonPath.setFillType(SkPathFillType::kEvenOdd);
859 addUpdateRegion(polygonPath.getBounds());
861 // For lines we use toSkX()/toSkY() in order to pass centers of pixels to Skia,
862 // as that leads to better results with floating-point coordinates
863 // (e.g. https://bugs.chromium.org/p/skia/issues/detail?id=9611).
864 // But that means that we generally need to use it also for areas, so that they
865 // line up properly if used together (tdf#134346).
866 // On the other hand, with AA enabled and rectangular areas, this leads to fuzzy
867 // edges (tdf#137329). But since rectangular areas line up perfectly to pixels
868 // everywhere, it shouldn't be necessary to do this for them.
869 // So if AA is enabled, avoid this fixup for rectangular areas.
870 if (!useAA || !hasOnlyOrthogonal)
872 // We normally use pixel at their center positions, but slightly off (see toSkX/Y()).
873 // With AA lines that "slightly off" causes tiny changes of color, making some tests
874 // fail. Since moving AA-ed line slightly to a side doesn't cause any real visual
875 // difference, just place exactly at the center. tdf#134346
876 const SkScalar posFix = useAA ? toSkXYFix : 0;
877 polygonPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
879 if (mFillColor != SALCOLOR_NONE)
881 SkPaint aPaint = makeFillPaint(fTransparency);
882 aPaint.setAntiAlias(useAA);
883 // HACK: If the polygon is just a line, it still should be drawn. But when filling
884 // Skia doesn't draw empty polygons, so in that case ensure the line is drawn.
885 if (mLineColor == SALCOLOR_NONE && polygonPath.getBounds().isEmpty())
886 aPaint.setStyle(SkPaint::kStroke_Style);
887 getDrawCanvas()->drawPath(polygonPath, aPaint);
889 if (mLineColor != SALCOLOR_NONE && mLineColor != mFillColor) // otherwise handled by fill
891 SkPaint aPaint = makeLinePaint(fTransparency);
892 aPaint.setAntiAlias(useAA);
893 getDrawCanvas()->drawPath(polygonPath, aPaint);
895 postDraw();
898 namespace
900 struct LessThan
902 bool operator()(const basegfx::B2DPoint& point1, const basegfx::B2DPoint& point2) const
904 if (basegfx::fTools::equal(point1.getX(), point2.getX()))
905 return basegfx::fTools::less(point1.getY(), point2.getY());
906 return basegfx::fTools::less(point1.getX(), point2.getX());
909 } // namespace
911 bool SkiaSalGraphicsImpl::delayDrawPolyPolygon(const basegfx::B2DPolyPolygon& aPolyPolygon,
912 double fTransparency)
914 // There is some code that needlessly subdivides areas into adjacent rectangles,
915 // but Skia doesn't line them up perfectly if AA is enabled (e.g. Cairo, Qt5 do,
916 // but Skia devs claim it's working as intended
917 // https://groups.google.com/d/msg/skia-discuss/NlKpD2X_5uc/Vuwd-kyYBwAJ).
918 // An example is tdf#133016, which triggers SvgStyleAttributes::add_stroke()
919 // implementing a line stroke as a bunch of polygons instead of just one, and
920 // SvgLinearAtomPrimitive2D::create2DDecomposition() creates a gradient
921 // as a series of polygons of gradually changing color. Those places should be
922 // changed, but try to merge those split polygons back into the original one,
923 // where the needlessly created edges causing problems will not exist.
924 // This means drawing of such polygons needs to be delayed, so that they can
925 // be possibly merged with the next one.
926 // Merge only polygons of the same properties (color, etc.), so the gradient problem
927 // actually isn't handled here.
929 // Only AA polygons need merging, because they do not line up well because of the AA of the edges.
930 if (!mParent.getAntiAlias())
931 return false;
932 // Only filled polygons without an outline are problematic.
933 if (mFillColor == SALCOLOR_NONE || mLineColor != SALCOLOR_NONE)
934 return false;
935 // Merge only simple polygons, real polypolygons most likely aren't needlessly split,
936 // so they do not need joining.
937 if (aPolyPolygon.count() != 1)
938 return false;
939 // If the polygon is not closed, it doesn't mark an area to be filled.
940 if (!aPolyPolygon.isClosed())
941 return false;
942 // If a polygon does not contain a straight line, i.e. it's all curves, then do not merge.
943 // First of all that's even more expensive, and second it's very unlikely that it's a polygon
944 // split into more polygons.
945 if (!polygonContainsLine(aPolyPolygon))
946 return false;
948 if (mLastPolyPolygonInfo.polygons.size() != 0
949 && (mLastPolyPolygonInfo.transparency != fTransparency
950 || !mLastPolyPolygonInfo.bounds.overlaps(aPolyPolygon.getB2DRange())))
952 checkPendingDrawing(); // Cannot be parts of the same larger polygon, draw the last and reset.
954 if (!mLastPolyPolygonInfo.polygons.empty())
956 assert(aPolyPolygon.count() == 1);
957 assert(mLastPolyPolygonInfo.polygons.back().count() == 1);
958 // Check if the new and the previous polygon share at least one point. If not, then they
959 // cannot be adjacent polygons, so there's no point in trying to merge them.
960 bool sharePoint = false;
961 const basegfx::B2DPolygon& poly1 = aPolyPolygon.getB2DPolygon(0);
962 const basegfx::B2DPolygon& poly2 = mLastPolyPolygonInfo.polygons.back().getB2DPolygon(0);
963 o3tl::sorted_vector<basegfx::B2DPoint, LessThan> poly1Points; // for O(n log n)
964 poly1Points.reserve(poly1.count());
965 for (sal_uInt32 i = 0; i < poly1.count(); ++i)
966 poly1Points.insert(poly1.getB2DPoint(i));
967 for (sal_uInt32 i = 0; i < poly2.count(); ++i)
968 if (poly1Points.find(poly2.getB2DPoint(i)) != poly1Points.end())
970 sharePoint = true;
971 break;
973 if (!sharePoint)
974 checkPendingDrawing(); // Draw the previous one and reset.
976 // Collect the polygons that can be possibly merged. Do the merging only once at the end,
977 // because it's not a cheap operation.
978 mLastPolyPolygonInfo.polygons.push_back(aPolyPolygon);
979 mLastPolyPolygonInfo.bounds.expand(aPolyPolygon.getB2DRange());
980 mLastPolyPolygonInfo.transparency = fTransparency;
981 return true;
984 // Tdf#140848 - basegfx::utils::mergeToSinglePolyPolygon() seems to have rounding
985 // errors that sometimes cause it to merge incorrectly.
986 static void roundPolygonPoints(basegfx::B2DPolyPolygon& polyPolygon)
988 for (basegfx::B2DPolygon& polygon : polyPolygon)
990 polygon.makeUnique();
991 for (sal_uInt32 i = 0; i < polygon.count(); ++i)
992 polygon.setB2DPoint(i, basegfx::B2DPoint(basegfx::fround(polygon.getB2DPoint(i))));
993 // Control points are saved as vectors relative to points, so hopefully
994 // there's no need to round those.
998 void SkiaSalGraphicsImpl::checkPendingDrawing()
1000 if (mLastPolyPolygonInfo.polygons.size() != 0)
1001 { // Flush any pending polygon drawing.
1002 basegfx::B2DPolyPolygonVector polygons;
1003 std::swap(polygons, mLastPolyPolygonInfo.polygons);
1004 double transparency = mLastPolyPolygonInfo.transparency;
1005 mLastPolyPolygonInfo.bounds.reset();
1006 if (polygons.size() == 1)
1007 performDrawPolyPolygon(polygons.front(), transparency, true);
1008 else
1010 for (basegfx::B2DPolyPolygon& p : polygons)
1011 roundPolygonPoints(p);
1012 performDrawPolyPolygon(basegfx::utils::mergeToSinglePolyPolygon(polygons), transparency,
1013 true);
1018 bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
1019 const basegfx::B2DPolygon& rPolyLine, double fTransparency,
1020 double fLineWidth, const std::vector<double>* pStroke,
1021 basegfx::B2DLineJoin eLineJoin,
1022 css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
1023 bool bPixelSnapHairline)
1025 if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0
1026 || mLineColor == SALCOLOR_NONE)
1028 return true;
1031 preDraw();
1032 SAL_INFO("vcl.skia.trace", "drawpolyline(" << this << "): " << rPolyLine << ":" << mLineColor);
1034 // Adjust line width for object-to-device scale.
1035 fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
1036 // On HiDPI displays, do not draw hairlines, draw 1-pixel wide lines in order to avoid
1037 // smoothing that would confuse unittests.
1038 if (fLineWidth == 0 && mScaling != 1 && isUnitTestRunning())
1039 fLineWidth = 1; // this will be scaled by mScaling
1041 // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
1042 basegfx::B2DPolygon aPolyLine(rPolyLine);
1043 aPolyLine.transform(rObjectToDevice);
1044 if (bPixelSnapHairline)
1046 aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);
1049 SkPaint aPaint = makeLinePaint(fTransparency);
1051 switch (eLineJoin)
1053 case basegfx::B2DLineJoin::Bevel:
1054 aPaint.setStrokeJoin(SkPaint::kBevel_Join);
1055 break;
1056 case basegfx::B2DLineJoin::Round:
1057 aPaint.setStrokeJoin(SkPaint::kRound_Join);
1058 break;
1059 case basegfx::B2DLineJoin::NONE:
1060 break;
1061 case basegfx::B2DLineJoin::Miter:
1062 aPaint.setStrokeJoin(SkPaint::kMiter_Join);
1063 // convert miter minimum angle to miter limit
1064 aPaint.setStrokeMiter(1.0 / std::sin(fMiterMinimumAngle / 2.0));
1065 break;
1068 switch (eLineCap)
1070 case css::drawing::LineCap_ROUND:
1071 aPaint.setStrokeCap(SkPaint::kRound_Cap);
1072 break;
1073 case css::drawing::LineCap_SQUARE:
1074 aPaint.setStrokeCap(SkPaint::kSquare_Cap);
1075 break;
1076 default: // css::drawing::LineCap_BUTT:
1077 aPaint.setStrokeCap(SkPaint::kButt_Cap);
1078 break;
1081 aPaint.setStrokeWidth(fLineWidth);
1082 aPaint.setAntiAlias(mParent.getAntiAlias());
1083 // See the tdf#134346 comment above.
1084 const SkScalar posFix = mParent.getAntiAlias() ? toSkXYFix : 0;
1086 if (pStroke && std::accumulate(pStroke->begin(), pStroke->end(), 0.0) != 0)
1088 std::vector<SkScalar> intervals;
1089 // Transform size by the matrix.
1090 for (double stroke : *pStroke)
1091 intervals.push_back((rObjectToDevice * basegfx::B2DVector(stroke, 0)).getLength());
1092 aPaint.setPathEffect(SkDashPathEffect::Make(intervals.data(), intervals.size(), 0));
1095 // Skia does not support basegfx::B2DLineJoin::NONE, so in that case batch only if lines
1096 // are not wider than a pixel.
1097 if (eLineJoin != basegfx::B2DLineJoin::NONE || fLineWidth <= 1.0)
1099 SkPath aPath;
1100 aPath.incReserve(aPolyLine.count() * 3); // because cubicTo is 3 elements
1101 addPolygonToPath(aPolyLine, aPath);
1102 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1103 addUpdateRegion(aPath.getBounds());
1104 getDrawCanvas()->drawPath(aPath, aPaint);
1106 else
1108 sal_uInt32 nPoints = aPolyLine.count();
1109 bool bClosed = aPolyLine.isClosed();
1110 bool bHasCurves = aPolyLine.areControlPointsUsed();
1111 for (sal_uInt32 j = 0; j < nPoints; ++j)
1113 SkPath aPath;
1114 aPath.incReserve(2 * 3); // because cubicTo is 3 elements
1115 addPolygonToPath(aPolyLine, aPath, j, j + 1, nPoints, bClosed, bHasCurves);
1116 aPath.offset(toSkX(0) + posFix, toSkY(0) + posFix, nullptr);
1117 addUpdateRegion(aPath.getBounds());
1118 getDrawCanvas()->drawPath(aPath, aPaint);
1122 postDraw();
1124 return true;
1127 bool SkiaSalGraphicsImpl::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*)
1129 return false;
1132 bool SkiaSalGraphicsImpl::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*)
1134 return false;
1137 bool SkiaSalGraphicsImpl::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*, const Point* const*,
1138 const PolyFlags* const*)
1140 return false;
1143 void SkiaSalGraphicsImpl::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
1144 tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
1145 bool /*bWindowInvalidate*/)
1147 if (nDestX == nSrcX && nDestY == nSrcY)
1148 return;
1149 preDraw();
1150 SAL_INFO("vcl.skia.trace", "copyarea("
1151 << this << "): " << Point(nSrcX, nSrcY) << "->"
1152 << SkIRect::MakeXYWH(nDestX, nDestY, nSrcWidth, nSrcHeight));
1153 // Using SkSurface::draw() should be more efficient, but it's too buggy.
1154 SalTwoRect rPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
1155 privateCopyBits(rPosAry, this);
1156 postDraw();
1159 void SkiaSalGraphicsImpl::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
1161 preDraw();
1162 SkiaSalGraphicsImpl* src;
1163 if (pSrcGraphics)
1165 assert(dynamic_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl()));
1166 src = static_cast<SkiaSalGraphicsImpl*>(pSrcGraphics->GetImpl());
1167 src->checkSurface();
1168 src->flushDrawing();
1170 else
1172 src = this;
1173 assert(mXorMode == XorMode::None);
1175 auto srcDebug = [&]() -> std::string {
1176 if (src == this)
1177 return "(self)";
1178 else
1180 std::ostringstream stream;
1181 stream << "(" << src << ")";
1182 return stream.str();
1185 SAL_INFO("vcl.skia.trace", "copybits(" << this << "): " << srcDebug() << ": " << rPosAry);
1186 privateCopyBits(rPosAry, src);
1187 postDraw();
1190 void SkiaSalGraphicsImpl::privateCopyBits(const SalTwoRect& rPosAry, SkiaSalGraphicsImpl* src)
1192 assert(mXorMode == XorMode::None);
1193 addUpdateRegion(SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1194 rPosAry.mnDestHeight));
1195 SkPaint paint;
1196 paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha
1197 SkIRect srcRect = SkIRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth,
1198 rPosAry.mnSrcHeight);
1199 SkRect destRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1200 rPosAry.mnDestHeight);
1202 if (!SkIRect::Intersects(srcRect, SkIRect::MakeWH(src->GetWidth(), src->GetHeight()))
1203 || !SkRect::Intersects(destRect, SkRect::MakeWH(GetWidth(), GetHeight())))
1204 return;
1206 if (src == this)
1208 // Copy-to-self means that we'd take a snapshot, which would refcount the data,
1209 // and then drawing would result in copy in write, copying the entire surface.
1210 // Try to copy less by making a snapshot of only what is needed.
1211 // A complication here is that drawImageRect() can handle coordinates outside
1212 // of surface fine, but makeImageSnapshot() will crop to the surface area,
1213 // so do that manually here in order to adjust also destination rectangle.
1214 if (srcRect.x() < 0 || srcRect.y() < 0)
1216 destRect.fLeft += -srcRect.x();
1217 destRect.fTop += -srcRect.y();
1218 srcRect.adjust(-srcRect.x(), -srcRect.y(), 0, 0);
1220 // Note that right() and bottom() are not inclusive (are outside of the rect).
1221 if (srcRect.right() - 1 > GetWidth() || srcRect.bottom() - 1 > GetHeight())
1223 destRect.fRight += GetWidth() - srcRect.right();
1224 destRect.fBottom += GetHeight() - srcRect.bottom();
1225 srcRect.adjust(0, 0, GetWidth() - srcRect.right(), GetHeight() - srcRect.bottom());
1227 // Scaling for source coordinates must be done manually.
1228 if (src->mScaling != 1)
1229 srcRect = scaleRect(srcRect, src->mScaling);
1230 sk_sp<SkImage> image = makeCheckedImageSnapshot(src->mSurface, srcRect);
1231 srcRect.offset(-srcRect.x(), -srcRect.y());
1232 getDrawCanvas()->drawImageRect(image, SkRect::Make(srcRect), destRect,
1233 makeSamplingOptions(rPosAry, mScaling, src->mScaling),
1234 &paint, SkCanvas::kFast_SrcRectConstraint);
1236 else
1238 // Scaling for source coordinates must be done manually.
1239 if (src->mScaling != 1)
1240 srcRect = scaleRect(srcRect, src->mScaling);
1241 // Do not use makeImageSnapshot(rect), as that one may make a needless data copy.
1242 getDrawCanvas()->drawImageRect(makeCheckedImageSnapshot(src->mSurface),
1243 SkRect::Make(srcRect), destRect,
1244 makeSamplingOptions(rPosAry, mScaling, src->mScaling),
1245 &paint, SkCanvas::kFast_SrcRectConstraint);
1249 bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect& rPosAry, const SalBitmap& rBitmap)
1251 if (checkInvalidSourceOrDestination(rPosAry))
1252 return false;
1254 assert(dynamic_cast<const SkiaSalBitmap*>(&rBitmap));
1255 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rBitmap);
1256 // This is used by VirtualDevice in the alpha mode for the "alpha" layer which
1257 // is actually one-minus-alpha (opacity). Therefore white=0xff=transparent,
1258 // black=0x00=opaque. So the result is transparent only if both the inputs
1259 // are transparent. Since for blending operations white=1.0 and black=0.0,
1260 // kMultiply should handle exactly that (transparent*transparent=transparent,
1261 // opaque*transparent=opaque). And guessing from the "floor" in TYPE_BLEND in opengl's
1262 // combinedTextureFragmentShader.glsl, the layer is not even alpha values but
1263 // simply yes-or-no mask.
1264 // See also blendAlphaBitmap().
1265 if (rSkiaBitmap.IsFullyOpaqueAsAlpha())
1267 // Optimization. If the bitmap means fully opaque, it's all zero's. In CPU
1268 // mode it should be faster to just copy instead of SkBlendMode::kMultiply.
1269 drawBitmap(rPosAry, rSkiaBitmap);
1271 else
1272 drawBitmap(rPosAry, rSkiaBitmap, SkBlendMode::kMultiply);
1273 return true;
1276 bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry,
1277 const SalBitmap& rSourceBitmap,
1278 const SalBitmap& rMaskBitmap,
1279 const SalBitmap& rAlphaBitmap)
1281 if (checkInvalidSourceOrDestination(rPosAry))
1282 return false;
1284 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1285 assert(dynamic_cast<const SkiaSalBitmap*>(&rMaskBitmap));
1286 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1287 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1288 const SkiaSalBitmap& rSkiaMaskBitmap = static_cast<const SkiaSalBitmap&>(rMaskBitmap);
1289 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1291 if (rSkiaMaskBitmap.IsFullyOpaqueAsAlpha())
1293 // Optimization. If the mask of the bitmap to be blended means it's actually opaque,
1294 // just draw the bitmap directly (that's what the math below will result in).
1295 drawBitmap(rPosAry, rSkiaSourceBitmap);
1296 return true;
1298 // This was originally implemented for the OpenGL drawing method and it is poorly documented.
1299 // The source and mask bitmaps are the usual data and alpha bitmaps, and 'alpha'
1300 // is the "alpha" layer of the VirtualDevice (the alpha in VirtualDevice is also stored
1301 // as a separate bitmap). Now if I understand it correctly these two alpha masks first need
1302 // to be combined into the actual alpha mask to be used. The formula for TYPE_BLEND
1303 // in opengl's combinedTextureFragmentShader.glsl is
1304 // "result_alpha = 1.0 - (1.0 - floor(alpha)) * mask".
1305 // See also blendBitmap().
1307 SkSamplingOptions samplingOptions = makeSamplingOptions(rPosAry, mScaling);
1308 // First do the "( 1 - alpha ) * mask"
1309 // (no idea how to do "floor", but hopefully not needed in practice).
1310 sk_sp<SkShader> shaderAlpha
1311 = SkShaders::Blend(SkBlendMode::kDstOut, rSkiaMaskBitmap.GetAlphaSkShader(samplingOptions),
1312 rSkiaAlphaBitmap.GetAlphaSkShader(samplingOptions));
1313 // And now draw the bitmap with "1 - x", where x is the "( 1 - alpha ) * mask".
1314 sk_sp<SkShader> shader = SkShaders::Blend(SkBlendMode::kSrcOut, shaderAlpha,
1315 rSkiaSourceBitmap.GetSkShader(samplingOptions));
1316 drawShader(rPosAry, shader);
1317 return true;
1320 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
1322 if (checkInvalidSourceOrDestination(rPosAry))
1323 return;
1325 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1326 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1328 drawBitmap(rPosAry, rSkiaSourceBitmap);
1331 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1332 const SalBitmap& rMaskBitmap)
1334 drawAlphaBitmap(rPosAry, rSalBitmap, rMaskBitmap);
1337 void SkiaSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap,
1338 Color nMaskColor)
1340 assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap));
1341 const SkiaSalBitmap& skiaBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap);
1342 drawShader(
1343 rPosAry,
1344 SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1345 SkShaders::Color(toSkColor(nMaskColor)),
1346 skiaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
1349 std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(tools::Long nX, tools::Long nY,
1350 tools::Long nWidth, tools::Long nHeight)
1352 SkiaZone zone;
1353 checkSurface();
1354 SAL_INFO("vcl.skia.trace",
1355 "getbitmap(" << this << "): " << SkIRect::MakeXYWH(nX, nY, nWidth, nHeight));
1356 flushDrawing();
1357 // TODO makeImageSnapshot(rect) may copy the data, which may be a waste if this is used
1358 // e.g. for VirtualDevice's lame alpha blending, in which case the image will eventually end up
1359 // in blendAlphaBitmap(), where we could simply use the proper rect of the image.
1360 sk_sp<SkImage> image = makeCheckedImageSnapshot(
1361 mSurface, scaleRect(SkIRect::MakeXYWH(nX, nY, nWidth, nHeight), mScaling));
1362 std::shared_ptr<SkiaSalBitmap> bitmap = std::make_shared<SkiaSalBitmap>(image);
1363 // If the surface is scaled for HiDPI, the bitmap needs to be scaled down, otherwise
1364 // it would have incorrect size from the API point of view. The DirectImage::Yes handling
1365 // in mergeCacheBitmaps() should access the original unscaled bitmap data to avoid
1366 // pointless scaling back and forth.
1367 if (mScaling != 1)
1369 if (!isUnitTestRunning())
1370 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, goodScalingQuality());
1371 else
1373 // Some tests require exact pixel values and would be confused by smooth-scaling.
1374 // And some draw something smooth and not smooth-scaling there would break the checks.
1375 if (isUnitTestRunning("BackendTest__testDrawHaflEllipseAAWithPolyLineB2D_")
1376 || isUnitTestRunning("BackendTest__testDrawRectAAWithLine_")
1377 || isUnitTestRunning("GraphicsRenderTest__testDrawRectAAWithLine"))
1379 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, goodScalingQuality());
1381 else
1382 bitmap->Scale(1.0 / mScaling, 1.0 / mScaling, BmpScaleFlag::NearestNeighbor);
1385 return bitmap;
1388 Color SkiaSalGraphicsImpl::getPixel(tools::Long nX, tools::Long nY)
1390 SkiaZone zone;
1391 checkSurface();
1392 SAL_INFO("vcl.skia.trace", "getpixel(" << this << "): " << Point(nX, nY));
1393 flushDrawing();
1394 // This is presumably slow, but getPixel() should be generally used only by unit tests.
1395 SkBitmap bitmap;
1396 if (!bitmap.tryAllocN32Pixels(mSurface->width(), mSurface->height()))
1397 abort();
1398 if (!mSurface->readPixels(bitmap, 0, 0))
1399 abort();
1400 return fromSkColor(bitmap.getColor(nX * mScaling, nY * mScaling));
1403 void SkiaSalGraphicsImpl::invert(basegfx::B2DPolygon const& rPoly, SalInvert eFlags)
1405 preDraw();
1406 SAL_INFO("vcl.skia.trace", "invert(" << this << "): " << rPoly << ":" << int(eFlags));
1407 assert(mXorMode == XorMode::None);
1408 SkPath aPath;
1409 aPath.incReserve(rPoly.count());
1410 addPolygonToPath(rPoly, aPath);
1411 aPath.setFillType(SkPathFillType::kEvenOdd);
1412 addUpdateRegion(aPath.getBounds());
1413 SkAutoCanvasRestore autoRestore(getDrawCanvas(), true);
1414 SkPaint aPaint;
1415 // There's no blend mode for inverting as such, but kExclusion is 's + d - 2*s*d',
1416 // so with d = 1.0 (all channels) it becomes effectively '1 - s', i.e. inverted color.
1417 aPaint.setBlendMode(SkBlendMode::kExclusion);
1418 aPaint.setColor(SkColorSetARGB(255, 255, 255, 255));
1419 // TrackFrame just inverts a dashed path around the polygon
1420 if (eFlags == SalInvert::TrackFrame)
1422 // TrackFrame is not supposed to paint outside of the polygon (usually rectangle),
1423 // but wider stroke width usually results in that, so ensure the requirement
1424 // by clipping.
1425 getDrawCanvas()->clipRect(aPath.getBounds(), SkClipOp::kIntersect, false);
1426 aPaint.setStrokeWidth(2);
1427 constexpr float intervals[] = { 4.0f, 4.0f };
1428 aPaint.setStyle(SkPaint::kStroke_Style);
1429 aPaint.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
1431 else
1433 aPaint.setStyle(SkPaint::kFill_Style);
1435 // N50 inverts in checker pattern
1436 if (eFlags == SalInvert::N50)
1438 // This creates 2x2 checker pattern bitmap
1439 // TODO Use createSkSurface() and cache the image
1440 SkBitmap aBitmap;
1441 aBitmap.allocN32Pixels(2, 2);
1442 const SkPMColor white = SkPreMultiplyARGB(0xFF, 0xFF, 0xFF, 0xFF);
1443 const SkPMColor black = SkPreMultiplyARGB(0xFF, 0x00, 0x00, 0x00);
1444 SkPMColor* scanline;
1445 scanline = aBitmap.getAddr32(0, 0);
1446 *scanline++ = white;
1447 *scanline++ = black;
1448 scanline = aBitmap.getAddr32(0, 1);
1449 *scanline++ = black;
1450 *scanline++ = white;
1451 aBitmap.setImmutable();
1452 // The bitmap is repeated in both directions the checker pattern is as big
1453 // as the polygon (usually rectangle)
1454 aPaint.setShader(
1455 aBitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()));
1458 getDrawCanvas()->drawPath(aPath, aPaint);
1459 postDraw();
1462 void SkiaSalGraphicsImpl::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
1463 tools::Long nHeight, SalInvert eFlags)
1465 basegfx::B2DRectangle aRectangle(nX, nY, nX + nWidth, nY + nHeight);
1466 auto aRect = basegfx::utils::createPolygonFromRect(aRectangle);
1467 invert(aRect, eFlags);
1470 void SkiaSalGraphicsImpl::invert(sal_uInt32 nPoints, const Point* pPointArray, SalInvert eFlags)
1472 basegfx::B2DPolygon aPolygon;
1473 aPolygon.append(basegfx::B2DPoint(pPointArray[0].getX(), pPointArray[0].getY()), nPoints);
1474 for (sal_uInt32 i = 1; i < nPoints; ++i)
1476 aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPointArray[i].getX(), pPointArray[i].getY()));
1478 aPolygon.setClosed(true);
1480 invert(aPolygon, eFlags);
1483 bool SkiaSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*,
1484 sal_uInt32)
1486 return false;
1489 // Create SkImage from a bitmap and possibly an alpha mask (the usual VCL one-minus-alpha),
1490 // with the given target size. Result will be possibly cached, unless disabled.
1491 // Especially in raster mode scaling and alpha blending may be expensive if done repeatedly.
1492 sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitmap,
1493 const SkiaSalBitmap* alphaBitmap,
1494 const Size& targetSize)
1496 if (alphaBitmap)
1497 assert(bitmap.GetSize() == alphaBitmap->GetSize());
1499 if (targetSize.IsEmpty())
1500 return {};
1501 if (alphaBitmap && alphaBitmap->IsFullyOpaqueAsAlpha())
1502 alphaBitmap = nullptr; // the alpha can be ignored
1503 if (bitmap.PreferSkShader() && (!alphaBitmap || alphaBitmap->PreferSkShader()))
1504 return {};
1506 // If the bitmap has SkImage that matches the required size, try to use it, even
1507 // if it doesn't match bitmap.GetSize(). This can happen with delayed scaling.
1508 // This will catch cases such as some code pre-scaling the bitmap, which would make GetSkImage()
1509 // scale, changing GetImageKey() in the process so we'd have to re-cache, and then we'd need
1510 // to scale again in this function.
1511 bool bitmapReady = false;
1512 bool alphaBitmapReady = false;
1513 if (const sk_sp<SkImage>& image = bitmap.GetSkImage(DirectImage::Yes))
1515 assert(!bitmap.PreferSkShader());
1516 if (imageSize(image) == targetSize)
1517 bitmapReady = true;
1519 // If the image usable and there's no alpha, then it matches exactly what's wanted.
1520 if (bitmapReady && !alphaBitmap)
1521 return bitmap.GetSkImage(DirectImage::Yes);
1522 if (alphaBitmap)
1524 if (!alphaBitmap->GetAlphaSkImage(DirectImage::Yes)
1525 && alphaBitmap->GetSkImage(DirectImage::Yes)
1526 && imageSize(alphaBitmap->GetSkImage(DirectImage::Yes)) == targetSize)
1528 // There's a usable non-alpha image, try to convert it to alpha.
1529 assert(!alphaBitmap->PreferSkShader());
1530 const_cast<SkiaSalBitmap*>(alphaBitmap)->TryDirectConvertToAlphaNoScaling();
1532 if (const sk_sp<SkImage>& image = alphaBitmap->GetAlphaSkImage(DirectImage::Yes))
1534 assert(!alphaBitmap->PreferSkShader());
1535 if (imageSize(image) == targetSize)
1536 alphaBitmapReady = true;
1540 if (bitmapReady && (!alphaBitmap || alphaBitmapReady))
1542 // Try to find a cached image based on the already existing images.
1543 OString key = makeCachedImageKey(bitmap, alphaBitmap, targetSize, DirectImage::Yes,
1544 DirectImage::Yes);
1545 if (sk_sp<SkImage> image = findCachedImage(key))
1547 assert(imageSize(image) == targetSize);
1548 return image;
1552 // Probably not much point in caching of just doing a copy.
1553 if (alphaBitmap == nullptr && targetSize == bitmap.GetSize())
1554 return {};
1555 // Image too small to be worth caching if not scaling.
1556 if (targetSize == bitmap.GetSize() && targetSize.Width() < 100 && targetSize.Height() < 100)
1557 return {};
1558 // GPU-accelerated drawing with SkShader should be fast enough to not need caching.
1559 if (isGPU())
1561 // tdf#140925: But if this is such an extensive downscaling that caching the result
1562 // would noticeably reduce amount of data processed by the GPU on repeated usage, do it.
1563 int reduceRatio = bitmap.GetSize().Width() * bitmap.GetSize().Height() / targetSize.Width()
1564 / targetSize.Height();
1565 if (reduceRatio < 10)
1566 return {};
1568 // Do not cache the result if it would take most of the cache and thus get evicted soon.
1569 if (targetSize.Width() * targetSize.Height() * 4 > maxImageCacheSize() * 0.7)
1570 return {};
1572 // Use ready direct image if they are both available, now even the size doesn't matter
1573 // (we'll scale as necessary and it's better to scale from the original). Require only
1574 // that they are the same size, or that one prefers a shader or doesn't exist
1575 // (i.e. avoid two images of different size).
1576 bitmapReady = bitmap.GetSkImage(DirectImage::Yes) != nullptr;
1577 alphaBitmapReady = alphaBitmap && alphaBitmap->GetAlphaSkImage(DirectImage::Yes) != nullptr;
1578 if (bitmapReady && alphaBitmap && !alphaBitmapReady && !alphaBitmap->PreferSkShader())
1579 bitmapReady = false;
1580 if (alphaBitmapReady && !bitmapReady && bitmap.PreferSkShader())
1581 alphaBitmapReady = false;
1583 DirectImage bitmapType = bitmapReady ? DirectImage::Yes : DirectImage::No;
1584 DirectImage alphaBitmapType = alphaBitmapReady ? DirectImage::Yes : DirectImage::No;
1586 // Try to find a cached result, this time after possible delayed scaling.
1587 OString key = makeCachedImageKey(bitmap, alphaBitmap, targetSize, bitmapType, alphaBitmapType);
1588 if (sk_sp<SkImage> image = findCachedImage(key))
1590 assert(imageSize(image) == targetSize);
1591 return image;
1594 // In some cases (tdf#134237) the target size may be very large. In that case it's
1595 // better to rely on Skia to clip and draw only the necessary, rather than prepare
1596 // a very large image only to not use most of it. Do this only after checking whether
1597 // the image is already cached, since it might have been already cached in a previous
1598 // call that had the draw area large enough to be seen as worth caching.
1599 const Size drawAreaSize = mClipRegion.GetBoundRect().GetSize() * mScaling;
1600 if (targetSize.Width() > drawAreaSize.Width() || targetSize.Height() > drawAreaSize.Height())
1602 // This is a bit tricky. The condition above just checks that at least a part of the resulting
1603 // image will not be used (it's larger then our drawing area). But this may often happen
1604 // when just scrolling a document with a large image, where the caching may very well be worth it.
1605 // Since the problem is mainly the cost of upscaling and then the size of the resulting bitmap,
1606 // compute a ratio of how much this is going to be scaled up, how much this is larger than
1607 // the drawing area, and then refuse to cache if it's too much.
1608 const double upscaleRatio
1609 = std::max(1.0, 1.0 * targetSize.Width() / bitmap.GetSize().Width()
1610 * targetSize.Height() / bitmap.GetSize().Height());
1611 const double oversizeRatio = 1.0 * targetSize.Width() / drawAreaSize.Width()
1612 * targetSize.Height() / drawAreaSize.Height();
1613 const double ratio = upscaleRatio * oversizeRatio;
1614 if (ratio > 4)
1616 SAL_INFO("vcl.skia.trace", "mergecachebitmaps("
1617 << this << "): not caching, ratio:" << ratio << ", "
1618 << bitmap.GetSize() << "->" << targetSize << " in "
1619 << drawAreaSize);
1620 return {};
1624 Size sourceSize;
1625 if (bitmapReady)
1626 sourceSize = imageSize(bitmap.GetSkImage(DirectImage::Yes));
1627 else if (alphaBitmapReady)
1628 sourceSize = imageSize(alphaBitmap->GetAlphaSkImage(DirectImage::Yes));
1629 else
1630 sourceSize = bitmap.GetSize();
1632 // Generate a new result and cache it.
1633 sk_sp<SkSurface> tmpSurface
1634 = createSkSurface(targetSize, alphaBitmap ? kPremul_SkAlphaType : bitmap.alphaType());
1635 if (!tmpSurface)
1636 return nullptr;
1637 SkCanvas* canvas = tmpSurface->getCanvas();
1638 SkAutoCanvasRestore autoRestore(canvas, true);
1639 SkPaint paint;
1640 SkSamplingOptions samplingOptions;
1641 if (targetSize != sourceSize)
1643 SkMatrix matrix;
1644 matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / sourceSize.Width());
1645 matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / sourceSize.Height());
1646 canvas->concat(matrix);
1647 if (!isUnitTestRunning()) // unittests want exact pixel values
1648 samplingOptions = makeSamplingOptions(matrix, 1);
1650 if (alphaBitmap != nullptr)
1652 canvas->clear(SK_ColorTRANSPARENT);
1653 paint.setShader(
1654 SkShaders::Blend(SkBlendMode::kDstOut, bitmap.GetSkShader(samplingOptions, bitmapType),
1655 alphaBitmap->GetAlphaSkShader(samplingOptions, alphaBitmapType)));
1656 canvas->drawPaint(paint);
1658 else if (bitmap.PreferSkShader())
1660 paint.setShader(bitmap.GetSkShader(samplingOptions, bitmapType));
1661 canvas->drawPaint(paint);
1663 else
1664 canvas->drawImage(bitmap.GetSkImage(bitmapType), 0, 0, samplingOptions, &paint);
1665 if (isGPU())
1666 SAL_INFO("vcl.skia.trace", "mergecachebitmaps(" << this << "): caching GPU downscaling:"
1667 << bitmap.GetSize() << "->" << targetSize);
1668 sk_sp<SkImage> image = makeCheckedImageSnapshot(tmpSurface);
1669 addCachedImage(key, image);
1670 return image;
1673 OString SkiaSalGraphicsImpl::makeCachedImageKey(const SkiaSalBitmap& bitmap,
1674 const SkiaSalBitmap* alphaBitmap,
1675 const Size& targetSize, DirectImage bitmapType,
1676 DirectImage alphaBitmapType)
1678 OString key = OString::number(targetSize.Width()) + "x" + OString::number(targetSize.Height())
1679 + "_" + bitmap.GetImageKey(bitmapType);
1680 if (alphaBitmap)
1681 key += "_" + alphaBitmap->GetAlphaImageKey(alphaBitmapType);
1682 return key;
1685 bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
1686 const SalBitmap& rAlphaBitmap)
1688 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1689 assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
1690 const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1691 const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
1692 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1693 // alpha blending or scaling.
1694 SalTwoRect imagePosAry(rPosAry);
1695 Size imageSize = rSourceBitmap.GetSize();
1696 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1697 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1698 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1699 && rPosAry.mnSrcWidth == rSourceBitmap.GetSize().Width()
1700 && rPosAry.mnSrcHeight == rSourceBitmap.GetSize().Height())
1702 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1703 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1704 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1706 sk_sp<SkImage> image
1707 = mergeCacheBitmaps(rSkiaSourceBitmap, &rSkiaAlphaBitmap, imageSize * mScaling);
1708 if (image)
1709 drawImage(imagePosAry, image, mScaling);
1710 else if (rSkiaAlphaBitmap.IsFullyOpaqueAsAlpha()
1711 && !rSkiaSourceBitmap.PreferSkShader()) // alpha can be ignored
1712 drawBitmap(rPosAry, rSkiaSourceBitmap);
1713 else
1714 drawShader(rPosAry,
1715 SkShaders::Blend(
1716 SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1717 rSkiaSourceBitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)),
1718 rSkiaAlphaBitmap.GetAlphaSkShader(makeSamplingOptions(rPosAry, mScaling))));
1719 return true;
1722 void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBitmap& bitmap,
1723 SkBlendMode blendMode)
1725 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1726 // scaling.
1727 SalTwoRect imagePosAry(rPosAry);
1728 Size imageSize = bitmap.GetSize();
1729 // If the bitmap will be scaled, prefer to do it in mergeCacheBitmaps(), if possible.
1730 if ((rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
1731 && rPosAry.mnSrcX == 0 && rPosAry.mnSrcY == 0
1732 && rPosAry.mnSrcWidth == bitmap.GetSize().Width()
1733 && rPosAry.mnSrcHeight == bitmap.GetSize().Height())
1735 imagePosAry.mnSrcWidth = imagePosAry.mnDestWidth;
1736 imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
1737 imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
1739 sk_sp<SkImage> image = mergeCacheBitmaps(bitmap, nullptr, imageSize * mScaling);
1740 if (image)
1741 drawImage(imagePosAry, image, mScaling, blendMode);
1742 else if (bitmap.PreferSkShader())
1743 drawShader(rPosAry, bitmap.GetSkShader(makeSamplingOptions(rPosAry, mScaling)), blendMode);
1744 else
1745 drawImage(rPosAry, bitmap.GetSkImage(), 1, blendMode);
1748 void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage,
1749 int srcScaling, SkBlendMode eBlendMode)
1751 SkRect aSourceRect
1752 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1753 if (srcScaling != 1)
1754 aSourceRect = scaleRect(aSourceRect, srcScaling);
1755 SkRect aDestinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY,
1756 rPosAry.mnDestWidth, rPosAry.mnDestHeight);
1758 SkPaint aPaint = makeBitmapPaint();
1759 aPaint.setBlendMode(eBlendMode);
1761 preDraw();
1762 SAL_INFO("vcl.skia.trace",
1763 "drawimage(" << this << "): " << rPosAry << ":" << SkBlendMode_Name(eBlendMode));
1764 addUpdateRegion(aDestinationRect);
1765 getDrawCanvas()->drawImageRect(aImage, aSourceRect, aDestinationRect,
1766 makeSamplingOptions(rPosAry, mScaling, srcScaling), &aPaint,
1767 SkCanvas::kFast_SrcRectConstraint);
1768 ++pendingOperationsToFlush; // tdf#136369
1769 postDraw();
1772 // SkShader can be used to merge multiple bitmaps with appropriate blend modes (e.g. when
1773 // merging a bitmap with its alpha mask).
1774 void SkiaSalGraphicsImpl::drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader,
1775 SkBlendMode blendMode)
1777 preDraw();
1778 SAL_INFO("vcl.skia.trace", "drawshader(" << this << "): " << rPosAry);
1779 SkRect destinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth,
1780 rPosAry.mnDestHeight);
1781 addUpdateRegion(destinationRect);
1782 SkPaint paint = makeBitmapPaint();
1783 paint.setBlendMode(blendMode);
1784 paint.setShader(shader);
1785 SkCanvas* canvas = getDrawCanvas();
1786 // Scaling needs to be done explicitly using a matrix.
1787 SkAutoCanvasRestore autoRestore(canvas, true);
1788 SkMatrix matrix = SkMatrix::Translate(rPosAry.mnDestX, rPosAry.mnDestY)
1789 * SkMatrix::Scale(1.0 * rPosAry.mnDestWidth / rPosAry.mnSrcWidth,
1790 1.0 * rPosAry.mnDestHeight / rPosAry.mnSrcHeight)
1791 * SkMatrix::Translate(-rPosAry.mnSrcX, -rPosAry.mnSrcY);
1792 #ifndef NDEBUG
1793 // Handle floating point imprecisions, round p1 to 2 decimal places.
1794 auto compareRounded = [](const SkPoint& p1, const SkPoint& p2) {
1795 return rtl::math::round(p1.x(), 2) == p2.x() && rtl::math::round(p1.y(), 2) == p2.y();
1797 #endif
1798 assert(compareRounded(matrix.mapXY(rPosAry.mnSrcX, rPosAry.mnSrcY),
1799 SkPoint::Make(rPosAry.mnDestX, rPosAry.mnDestY)));
1800 assert(compareRounded(
1801 matrix.mapXY(rPosAry.mnSrcX + rPosAry.mnSrcWidth, rPosAry.mnSrcY + rPosAry.mnSrcHeight),
1802 SkPoint::Make(rPosAry.mnDestX + rPosAry.mnDestWidth,
1803 rPosAry.mnDestY + rPosAry.mnDestHeight)));
1804 canvas->concat(matrix);
1805 SkRect sourceRect
1806 = SkRect::MakeXYWH(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
1807 canvas->drawRect(sourceRect, paint);
1808 postDraw();
1811 bool SkiaSalGraphicsImpl::hasFastDrawTransformedBitmap() const
1813 // Return true even in raster mode, even that way Skia is faster than e.g. GraphicObject
1814 // trying to handle stuff manually.
1815 return true;
1818 // Whether applying matrix needs image smoothing for the transformation.
1819 static bool matrixNeedsHighQuality(const SkMatrix& matrix)
1821 if (matrix.isIdentity())
1822 return false;
1823 if (matrix.isScaleTranslate())
1825 if (abs(matrix.getScaleX()) == 1 && abs(matrix.getScaleY()) == 1)
1826 return false; // Only at most flipping and keeping the size.
1827 return true;
1829 assert(!matrix.hasPerspective()); // we do not use this
1830 if (matrix.getScaleX() == 0 && matrix.getScaleY() == 0)
1832 // Rotating 90 or 270 degrees while keeping the size.
1833 if ((matrix.getSkewX() == 1 && matrix.getSkewY() == -1)
1834 || (matrix.getSkewX() == -1 && matrix.getSkewY() == 1))
1835 return false;
1837 return true;
1840 namespace SkiaTests
1842 bool matrixNeedsHighQuality(const SkMatrix& matrix) { return ::matrixNeedsHighQuality(matrix); }
1845 bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
1846 const basegfx::B2DPoint& rX,
1847 const basegfx::B2DPoint& rY,
1848 const SalBitmap& rSourceBitmap,
1849 const SalBitmap* pAlphaBitmap, double fAlpha)
1851 assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
1852 assert(!pAlphaBitmap || dynamic_cast<const SkiaSalBitmap*>(pAlphaBitmap));
1854 const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
1855 const SkiaSalBitmap* pSkiaAlphaBitmap = static_cast<const SkiaSalBitmap*>(pAlphaBitmap);
1857 if (pSkiaAlphaBitmap && pSkiaAlphaBitmap->IsFullyOpaqueAsAlpha())
1858 pSkiaAlphaBitmap = nullptr; // the alpha can be ignored
1860 // Setup the image transformation,
1861 // using the rNull, rX, rY points as destinations for the (0,0), (Width,0), (0,Height) source points.
1862 const basegfx::B2DVector aXRel = rX - rNull;
1863 const basegfx::B2DVector aYRel = rY - rNull;
1865 preDraw();
1866 SAL_INFO("vcl.skia.trace", "drawtransformedbitmap(" << this << "): " << rSourceBitmap.GetSize()
1867 << " " << rNull << ":" << rX << ":" << rY);
1869 addUpdateRegion(SkRect::MakeWH(GetWidth(), GetHeight())); // can't tell, use whole area
1870 // Use mergeCacheBitmaps(), which may decide to cache the result, avoiding repeated
1871 // alpha blending or scaling.
1872 // The extra fAlpha blending is not cached, with the assumption that it usually gradually changes
1873 // for each invocation.
1874 // Pass size * mScaling to mergeCacheBitmaps() so that it prepares the size that will be needed
1875 // after the mScaling-scaling matrix, but otherwise calculate everything else using the VCL coordinates.
1876 Size imageSize(round(aXRel.getLength()), round(aYRel.getLength()));
1877 sk_sp<SkImage> imageToDraw
1878 = mergeCacheBitmaps(rSkiaBitmap, pSkiaAlphaBitmap, imageSize * mScaling);
1879 if (imageToDraw)
1881 SkMatrix matrix;
1882 // Round sizes for scaling, so that sub-pixel differences don't
1883 // trigger unnecessary scaling. Image has already been scaled
1884 // by mergeCacheBitmaps() and we shouldn't scale here again
1885 // unless the drawing is also skewed.
1886 matrix.set(SkMatrix::kMScaleX, round(aXRel.getX()) / imageSize.Width());
1887 matrix.set(SkMatrix::kMScaleY, round(aYRel.getY()) / imageSize.Height());
1888 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / imageSize.Width());
1889 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / imageSize.Height());
1890 matrix.set(SkMatrix::kMTransX, rNull.getX());
1891 matrix.set(SkMatrix::kMTransY, rNull.getY());
1892 SkCanvas* canvas = getDrawCanvas();
1893 SkAutoCanvasRestore autoRestore(canvas, true);
1894 canvas->concat(matrix);
1895 SkSamplingOptions samplingOptions;
1896 // If the matrix changes geometry, we need to smooth-scale. If there's mScaling,
1897 // that's already been handled by mergeCacheBitmaps().
1898 if (matrixNeedsHighQuality(matrix))
1899 samplingOptions = makeSamplingOptions(matrix, 1);
1900 if (fAlpha == 1.0)
1902 // Specify sizes to scale the image size back if needed (because of mScaling).
1903 SkRect dstRect = SkRect::MakeWH(imageSize.Width(), imageSize.Height());
1904 SkRect srcRect = SkRect::MakeWH(imageToDraw->width(), imageToDraw->height());
1905 SkPaint paint = makeBitmapPaint();
1906 canvas->drawImageRect(imageToDraw, srcRect, dstRect, samplingOptions, &paint,
1907 SkCanvas::kFast_SrcRectConstraint);
1909 else
1911 SkPaint paint = makeBitmapPaint();
1912 // Scale the image size back if needed.
1913 SkMatrix scale = SkMatrix::Scale(1.0 / mScaling, 1.0 / mScaling);
1914 paint.setShader(SkShaders::Blend(
1915 SkBlendMode::kDstIn, imageToDraw->makeShader(samplingOptions, &scale),
1916 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
1917 canvas->drawRect(SkRect::MakeWH(imageSize.Width(), imageSize.Height()), paint);
1920 else
1922 SkMatrix matrix;
1923 const Size aSize = rSourceBitmap.GetSize();
1924 matrix.set(SkMatrix::kMScaleX, aXRel.getX() / aSize.Width());
1925 matrix.set(SkMatrix::kMScaleY, aYRel.getY() / aSize.Height());
1926 matrix.set(SkMatrix::kMSkewY, aXRel.getY() / aSize.Width());
1927 matrix.set(SkMatrix::kMSkewX, aYRel.getX() / aSize.Height());
1928 matrix.set(SkMatrix::kMTransX, rNull.getX());
1929 matrix.set(SkMatrix::kMTransY, rNull.getY());
1930 SkCanvas* canvas = getDrawCanvas();
1931 SkAutoCanvasRestore autoRestore(canvas, true);
1932 canvas->concat(matrix);
1933 SkSamplingOptions samplingOptions;
1934 if (matrixNeedsHighQuality(matrix) || (mScaling != 1 && !isUnitTestRunning()))
1935 samplingOptions = makeSamplingOptions(matrix, mScaling);
1936 if (pSkiaAlphaBitmap)
1938 SkPaint paint = makeBitmapPaint();
1939 paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
1940 rSkiaBitmap.GetSkShader(samplingOptions),
1941 pSkiaAlphaBitmap->GetAlphaSkShader(samplingOptions)));
1942 if (fAlpha != 1.0)
1943 paint.setShader(
1944 SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
1945 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
1946 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
1948 else if (rSkiaBitmap.PreferSkShader() || fAlpha != 1.0)
1950 SkPaint paint = makeBitmapPaint();
1951 paint.setShader(rSkiaBitmap.GetSkShader(samplingOptions));
1952 if (fAlpha != 1.0)
1953 paint.setShader(
1954 SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(),
1955 SkShaders::Color(SkColorSetARGB(fAlpha * 255, 0, 0, 0))));
1956 canvas->drawRect(SkRect::MakeWH(aSize.Width(), aSize.Height()), paint);
1958 else
1960 SkPaint paint = makeBitmapPaint();
1961 canvas->drawImage(rSkiaBitmap.GetSkImage(), 0, 0, samplingOptions, &paint);
1964 postDraw();
1965 return true;
1968 bool SkiaSalGraphicsImpl::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
1969 tools::Long nHeight, sal_uInt8 nTransparency)
1971 privateDrawAlphaRect(nX, nY, nWidth, nHeight, nTransparency / 100.0);
1972 return true;
1975 bool SkiaSalGraphicsImpl::drawGradient(const tools::PolyPolygon& rPolyPolygon,
1976 const Gradient& rGradient)
1978 if (rGradient.GetStyle() != GradientStyle::Linear
1979 && rGradient.GetStyle() != GradientStyle::Axial
1980 && rGradient.GetStyle() != GradientStyle::Radial)
1981 return false; // unsupported
1982 if (rGradient.GetSteps() != 0)
1983 return false; // We can't tell Skia how many colors to use in the gradient.
1984 preDraw();
1985 SAL_INFO("vcl.skia.trace", "drawgradient(" << this << "): " << rPolyPolygon.getB2DPolyPolygon()
1986 << ":" << static_cast<int>(rGradient.GetStyle()));
1987 tools::Rectangle boundRect(rPolyPolygon.GetBoundRect());
1988 if (boundRect.IsEmpty())
1989 return true;
1990 SkPath path;
1991 if (rPolyPolygon.IsRect())
1993 // Rect->Polygon conversion loses the right and bottom edge, fix that.
1994 path.addRect(SkRect::MakeXYWH(boundRect.getX(), boundRect.getY(), boundRect.GetWidth(),
1995 boundRect.GetHeight()));
1996 boundRect.AdjustRight(1);
1997 boundRect.AdjustBottom(1);
1999 else
2000 addPolyPolygonToPath(rPolyPolygon.getB2DPolyPolygon(), path);
2001 path.setFillType(SkPathFillType::kEvenOdd);
2002 addUpdateRegion(path.getBounds());
2004 Gradient aGradient(rGradient);
2005 tools::Rectangle aBoundRect;
2006 Point aCenter;
2007 aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10);
2008 aGradient.GetBoundRect(boundRect, aBoundRect, aCenter);
2010 SkColor startColor
2011 = toSkColorWithIntensity(rGradient.GetStartColor(), rGradient.GetStartIntensity());
2012 SkColor endColor = toSkColorWithIntensity(rGradient.GetEndColor(), rGradient.GetEndIntensity());
2014 sk_sp<SkShader> shader;
2015 if (rGradient.GetStyle() == GradientStyle::Linear)
2017 tools::Polygon aPoly(aBoundRect);
2018 aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
2019 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
2020 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
2021 SkColor colors[2] = { startColor, endColor };
2022 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
2023 shader = SkGradientShader::MakeLinear(points, colors, pos, 2, SkTileMode::kClamp);
2025 else if (rGradient.GetStyle() == GradientStyle::Axial)
2027 tools::Polygon aPoly(aBoundRect);
2028 aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
2029 SkPoint points[2] = { SkPoint::Make(toSkX(aPoly[0].X()), toSkY(aPoly[0].Y())),
2030 SkPoint::Make(toSkX(aPoly[1].X()), toSkY(aPoly[1].Y())) };
2031 SkColor colors[3] = { endColor, startColor, endColor };
2032 SkScalar border = SkDoubleToScalar(aGradient.GetBorder() / 100.0);
2033 SkScalar pos[3]
2034 = { std::min<SkScalar>(border, 0.5), 0.5, std::max<SkScalar>(1 - border, 0.5) };
2035 shader = SkGradientShader::MakeLinear(points, colors, pos, 3, SkTileMode::kClamp);
2037 else
2039 // Move the center by (-1,-1) (the default VCL algorithm is a bit off-center that way,
2040 // Skia is the opposite way).
2041 SkPoint center = SkPoint::Make(toSkX(aCenter.X()) - 1, toSkY(aCenter.Y()) - 1);
2042 SkScalar radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
2043 SkColor colors[2] = { endColor, startColor };
2044 SkScalar pos[2] = { SkDoubleToScalar(aGradient.GetBorder() / 100.0), 1.0 };
2045 shader = SkGradientShader::MakeRadial(center, radius, colors, pos, 2, SkTileMode::kClamp);
2048 SkPaint paint = makeGradientPaint();
2049 paint.setAntiAlias(mParent.getAntiAlias());
2050 paint.setShader(shader);
2051 getDrawCanvas()->drawPath(path, paint);
2052 postDraw();
2053 return true;
2056 bool SkiaSalGraphicsImpl::implDrawGradient(const basegfx::B2DPolyPolygon& rPolyPolygon,
2057 const SalGradient& rGradient)
2059 preDraw();
2060 SAL_INFO("vcl.skia.trace",
2061 "impldrawgradient(" << this << "): " << rPolyPolygon << ":" << rGradient.maPoint1
2062 << "->" << rGradient.maPoint2 << ":" << rGradient.maStops.size());
2064 SkPath path;
2065 addPolyPolygonToPath(rPolyPolygon, path);
2066 path.setFillType(SkPathFillType::kEvenOdd);
2067 addUpdateRegion(path.getBounds());
2069 SkPoint points[2]
2070 = { SkPoint::Make(toSkX(rGradient.maPoint1.getX()), toSkY(rGradient.maPoint1.getY())),
2071 SkPoint::Make(toSkX(rGradient.maPoint2.getX()), toSkY(rGradient.maPoint2.getY())) };
2072 std::vector<SkColor> colors;
2073 std::vector<SkScalar> pos;
2074 for (const SalGradientStop& stop : rGradient.maStops)
2076 colors.emplace_back(toSkColor(stop.maColor));
2077 pos.emplace_back(stop.mfOffset);
2079 sk_sp<SkShader> shader = SkGradientShader::MakeLinear(points, colors.data(), pos.data(),
2080 colors.size(), SkTileMode::kDecal);
2081 SkPaint paint = makeGradientPaint();
2082 paint.setAntiAlias(mParent.getAntiAlias());
2083 paint.setShader(shader);
2084 getDrawCanvas()->drawPath(path, paint);
2085 postDraw();
2086 return true;
2089 static double toRadian(Degree10 degree10th) { return toRadians(3600_deg10 - degree10th); }
2090 static double toCos(Degree10 degree10th) { return SkScalarCos(toRadian(degree10th)); }
2091 static double toSin(Degree10 degree10th) { return SkScalarSin(toRadian(degree10th)); }
2093 void SkiaSalGraphicsImpl::drawGenericLayout(const GenericSalLayout& layout, Color textColor,
2094 const SkFont& font, const SkFont& verticalFont)
2096 SkiaZone zone;
2097 std::vector<SkGlyphID> glyphIds;
2098 std::vector<SkRSXform> glyphForms;
2099 std::vector<bool> verticals;
2100 glyphIds.reserve(256);
2101 glyphForms.reserve(256);
2102 verticals.reserve(256);
2103 DevicePoint aPos;
2104 const GlyphItem* pGlyph;
2105 int nStart = 0;
2106 while (layout.GetNextGlyph(&pGlyph, aPos, nStart))
2108 glyphIds.push_back(pGlyph->glyphId());
2109 Degree10 angle = layout.GetOrientation();
2110 if (pGlyph->IsVertical())
2111 angle += 900_deg10;
2112 SkRSXform form = SkRSXform::Make(toCos(angle), toSin(angle), aPos.getX(), aPos.getY());
2113 glyphForms.emplace_back(std::move(form));
2114 verticals.emplace_back(pGlyph->IsVertical());
2116 if (glyphIds.empty())
2117 return;
2119 preDraw();
2120 auto getBoundRect = [&layout]() {
2121 tools::Rectangle rect;
2122 layout.GetBoundRect(rect);
2123 return rect;
2125 SAL_INFO("vcl.skia.trace", "drawtextblob(" << this << "): " << getBoundRect() << ", "
2126 << glyphIds.size() << " glyphs, " << textColor);
2128 // Vertical glyphs need a different font, so split drawing into runs that each
2129 // draw only consecutive horizontal or vertical glyphs.
2130 std::vector<bool>::const_iterator pos = verticals.cbegin();
2131 std::vector<bool>::const_iterator end = verticals.cend();
2132 while (pos != end)
2134 bool verticalRun = *pos;
2135 std::vector<bool>::const_iterator rangeEnd = std::find(pos + 1, end, !verticalRun);
2136 size_t index = pos - verticals.cbegin();
2137 size_t count = rangeEnd - pos;
2138 sk_sp<SkTextBlob> textBlob = SkTextBlob::MakeFromRSXform(
2139 glyphIds.data() + index, count * sizeof(SkGlyphID), glyphForms.data() + index,
2140 verticalRun ? verticalFont : font, SkTextEncoding::kGlyphID);
2141 addUpdateRegion(textBlob->bounds());
2142 SkPaint paint = makeTextPaint(textColor);
2143 getDrawCanvas()->drawTextBlob(textBlob, 0, 0, paint);
2144 pos = rangeEnd;
2146 postDraw();
2149 bool SkiaSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
2151 switch (eType)
2153 case OutDevSupportType::B2DDraw:
2154 case OutDevSupportType::TransparentRect:
2155 return true;
2156 default:
2157 return false;
2161 static int getScaling()
2163 // It makes sense to support the debugging flag on all platforms
2164 // for unittests purpose, even if the actual windows cannot do it.
2165 if (const char* env = getenv("SAL_FORCE_HIDPI_SCALING"))
2166 return atoi(env);
2167 return 1;
2170 int SkiaSalGraphicsImpl::getWindowScaling() const
2172 static const int scaling = getScaling();
2173 return scaling;
2176 void SkiaSalGraphicsImpl::dump(const char* file) const
2178 assert(mSurface.get());
2179 SkiaHelper::dump(mSurface, file);
2182 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */