bump product version to 6.3.0.0.beta1
[LibreOffice.git] / vcl / headless / svpgdi.cxx
blob0f6fda87fd5b40b2ee028dcd47d0b7ef2aee90b1
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 <memory>
21 #ifndef IOS
22 #include <headless/svpgdi.hxx>
23 #endif
24 #include <headless/svpbmp.hxx>
25 #include <headless/svpframe.hxx>
26 #include <headless/svpcairotextrender.hxx>
27 #include <headless/CustomWidgetDraw.hxx>
28 #include <saldatabasic.hxx>
30 #include <sal/log.hxx>
31 #include <tools/helpers.hxx>
32 #include <o3tl/safeint.hxx>
33 #include <vcl/BitmapTools.hxx>
34 #include <vcl/sysdata.hxx>
35 #include <config_cairo_canvas.h>
36 #include <basegfx/numeric/ftools.hxx>
37 #include <basegfx/range/b2drange.hxx>
38 #include <basegfx/range/b2ibox.hxx>
39 #include <basegfx/polygon/b2dpolypolygon.hxx>
40 #include <basegfx/polygon/b2dpolypolygontools.hxx>
41 #include <basegfx/polygon/b2dpolygon.hxx>
42 #include <basegfx/polygon/b2dpolygontools.hxx>
43 #include <basegfx/matrix/b2dhommatrix.hxx>
44 #include <basegfx/utils/systemdependentdata.hxx>
45 #include <basegfx/matrix/b2dhommatrixtools.hxx>
46 #include <comphelper/lok.hxx>
47 #include <unx/gendata.hxx>
48 #include <dlfcn.h>
50 #if ENABLE_CAIRO_CANVAS
51 # if defined CAIRO_VERSION && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0)
52 # define CAIRO_OPERATOR_DIFFERENCE (static_cast<cairo_operator_t>(23))
53 # endif
54 #endif
56 namespace
58 basegfx::B2DRange getClipBox(cairo_t* cr)
60 double x1, y1, x2, y2;
62 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
64 // support B2DRange::isEmpty()
65 if(0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
67 return basegfx::B2DRange(x1, y1, x2, y2);
70 return basegfx::B2DRange();
73 basegfx::B2DRange getFillDamage(cairo_t* cr)
75 double x1, y1, x2, y2;
77 cairo_fill_extents(cr, &x1, &y1, &x2, &y2);
79 // support B2DRange::isEmpty()
80 if(0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
82 return basegfx::B2DRange(x1, y1, x2, y2);
85 return basegfx::B2DRange();
88 basegfx::B2DRange getClippedFillDamage(cairo_t* cr)
90 basegfx::B2DRange aDamageRect(getFillDamage(cr));
91 aDamageRect.intersect(getClipBox(cr));
92 return aDamageRect;
95 basegfx::B2DRange getStrokeDamage(cairo_t* cr)
97 double x1, y1, x2, y2;
99 cairo_stroke_extents(cr, &x1, &y1, &x2, &y2);
101 // support B2DRange::isEmpty()
102 if(0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
104 return basegfx::B2DRange(x1, y1, x2, y2);
107 return basegfx::B2DRange();
110 basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr)
112 basegfx::B2DRange aDamageRect(getStrokeDamage(cr));
113 aDamageRect.intersect(getClipBox(cr));
114 return aDamageRect;
118 bool SvpSalGraphics::blendBitmap( const SalTwoRect&, const SalBitmap& /*rBitmap*/ )
120 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::blendBitmap case");
121 return false;
124 bool SvpSalGraphics::blendAlphaBitmap( const SalTwoRect&, const SalBitmap&, const SalBitmap&, const SalBitmap& )
126 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::blendAlphaBitmap case");
127 return false;
130 namespace
132 cairo_format_t getCairoFormat(const BitmapBuffer& rBuffer)
134 cairo_format_t nFormat;
135 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
136 assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 24 || rBuffer.mnBitCount == 1);
137 #else
138 assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 1);
139 #endif
141 if (rBuffer.mnBitCount == 32)
142 nFormat = CAIRO_FORMAT_ARGB32;
143 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
144 else if (rBuffer.mnBitCount == 24)
145 nFormat = CAIRO_FORMAT_RGB24_888;
146 #endif
147 else
148 nFormat = CAIRO_FORMAT_A1;
149 return nFormat;
152 void Toggle1BitTransparency(const BitmapBuffer& rBuf)
154 assert(rBuf.maPalette.GetBestIndex(BitmapColor(COL_BLACK)) == 0);
155 // TODO: make upper layers use standard alpha
156 if (getCairoFormat(rBuf) == CAIRO_FORMAT_A1)
158 const int nImageSize = rBuf.mnHeight * rBuf.mnScanlineSize;
159 unsigned char* pDst = rBuf.mpBits;
160 for (int i = nImageSize; --i >= 0; ++pDst)
161 *pDst = ~*pDst;
165 std::unique_ptr<BitmapBuffer> FastConvert24BitRgbTo32BitCairo(const BitmapBuffer* pSrc)
167 if (pSrc == nullptr)
168 return nullptr;
170 assert(pSrc->mnFormat == SVP_24BIT_FORMAT);
171 const long nWidth = pSrc->mnWidth;
172 const long nHeight = pSrc->mnHeight;
173 std::unique_ptr<BitmapBuffer> pDst(new BitmapBuffer);
174 pDst->mnFormat = (ScanlineFormat::N32BitTcArgb | ScanlineFormat::TopDown);
175 pDst->mnWidth = nWidth;
176 pDst->mnHeight = nHeight;
177 pDst->mnBitCount = 32;
178 pDst->maColorMask = pSrc->maColorMask;
179 pDst->maPalette = pSrc->maPalette;
181 long nScanlineBase;
182 const bool bFail = o3tl::checked_multiply<long>(pDst->mnBitCount, nWidth, nScanlineBase);
183 if (bFail)
185 SAL_WARN("vcl.gdi", "checked multiply failed");
186 pDst->mpBits = nullptr;
187 return nullptr;
190 pDst->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
191 if (pDst->mnScanlineSize < nScanlineBase/8)
193 SAL_WARN("vcl.gdi", "scanline calculation wraparound");
194 pDst->mpBits = nullptr;
195 return nullptr;
200 pDst->mpBits = new sal_uInt8[ pDst->mnScanlineSize * nHeight ];
202 catch (const std::bad_alloc&)
204 // memory exception, clean up
205 pDst->mpBits = nullptr;
206 return nullptr;
209 for (long y = 0; y < nHeight; ++y)
211 sal_uInt8* pS = pSrc->mpBits + y * pSrc->mnScanlineSize;
212 sal_uInt8* pD = pDst->mpBits + y * pDst->mnScanlineSize;
213 for (long x = 0; x < nWidth; ++x)
215 #if defined ANDROID
216 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N32BitTcRgba, "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
217 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N24BitTcRgb, "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
218 pD[0] = pS[0];
219 pD[1] = pS[1];
220 pD[2] = pS[2];
221 pD[3] = 0xff; // Alpha
222 #elif defined OSL_BIGENDIAN
223 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N32BitTcArgb, "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
224 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N24BitTcRgb, "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
225 pD[0] = 0xff; // Alpha
226 pD[1] = pS[0];
227 pD[2] = pS[1];
228 pD[3] = pS[2];
229 #else
230 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N32BitTcBgra, "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
231 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N24BitTcBgr, "Expected SVP_24BIT_FORMAT set to N24BitTcBgr");
232 pD[0] = pS[0];
233 pD[1] = pS[1];
234 pD[2] = pS[2];
235 pD[3] = 0xff; // Alpha
236 #endif
238 pS += 3;
239 pD += 4;
243 return pDst;
246 class SourceHelper
248 public:
249 explicit SourceHelper(const SalBitmap& rSourceBitmap, const bool bForceARGB32 = false)
250 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
251 : m_bForceARGB32(bForceARGB32)
252 #endif
254 const SvpSalBitmap& rSrcBmp = static_cast<const SvpSalBitmap&>(rSourceBitmap);
255 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
256 if ((rSrcBmp.GetBitCount() != 32 && rSrcBmp.GetBitCount() != 24) || bForceARGB32)
257 #else
258 (void)bForceARGB32;
259 if (rSrcBmp.GetBitCount() != 32)
260 #endif
262 //big stupid copy here
263 const BitmapBuffer* pSrc = rSrcBmp.GetBuffer();
264 const SalTwoRect aTwoRect = { 0, 0, pSrc->mnWidth, pSrc->mnHeight,
265 0, 0, pSrc->mnWidth, pSrc->mnHeight };
266 std::unique_ptr<BitmapBuffer> pTmp = (pSrc->mnFormat == SVP_24BIT_FORMAT
267 ? FastConvert24BitRgbTo32BitCairo(pSrc)
268 : StretchAndConvert(*pSrc, aTwoRect, SVP_CAIRO_FORMAT));
269 aTmpBmp.Create(std::move(pTmp));
271 assert(aTmpBmp.GetBitCount() == 32);
272 source = SvpSalGraphics::createCairoSurface(aTmpBmp.GetBuffer());
274 else
275 source = SvpSalGraphics::createCairoSurface(rSrcBmp.GetBuffer());
277 ~SourceHelper()
279 cairo_surface_destroy(source);
281 cairo_surface_t* getSurface()
283 return source;
285 void mark_dirty()
287 cairo_surface_mark_dirty(source);
289 unsigned char* getBits(sal_Int32 &rStride)
291 cairo_surface_flush(source);
293 unsigned char *mask_data = cairo_image_surface_get_data(source);
295 const cairo_format_t nFormat = cairo_image_surface_get_format(source);
296 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
297 if (!m_bForceARGB32)
298 assert(nFormat == CAIRO_FORMAT_RGB24_888 && "Expected RGB24_888 image");
299 else
300 #endif
301 assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here");
303 rStride = cairo_format_stride_for_width(nFormat, cairo_image_surface_get_width(source));
305 return mask_data;
307 private:
308 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
309 const bool m_bForceARGB32;
310 #endif
311 SvpSalBitmap aTmpBmp;
312 cairo_surface_t* source;
314 SourceHelper(const SourceHelper&) = delete;
315 SourceHelper& operator=(const SourceHelper&) = delete;
318 class MaskHelper
320 public:
321 explicit MaskHelper(const SalBitmap& rAlphaBitmap)
323 const SvpSalBitmap& rMask = static_cast<const SvpSalBitmap&>(rAlphaBitmap);
324 const BitmapBuffer* pMaskBuf = rMask.GetBuffer();
326 if (rAlphaBitmap.GetBitCount() == 8)
328 // the alpha values need to be inverted for Cairo
329 // so big stupid copy and invert here
330 const int nImageSize = pMaskBuf->mnHeight * pMaskBuf->mnScanlineSize;
331 pAlphaBits.reset( new unsigned char[nImageSize] );
332 memcpy(pAlphaBits.get(), pMaskBuf->mpBits, nImageSize);
334 // TODO: make upper layers use standard alpha
335 sal_uInt32* pLDst = reinterpret_cast<sal_uInt32*>(pAlphaBits.get());
336 for( int i = nImageSize/sizeof(sal_uInt32); --i >= 0; ++pLDst )
337 *pLDst = ~*pLDst;
338 assert(reinterpret_cast<unsigned char*>(pLDst) == pAlphaBits.get()+nImageSize);
340 mask = cairo_image_surface_create_for_data(pAlphaBits.get(),
341 CAIRO_FORMAT_A8,
342 pMaskBuf->mnWidth, pMaskBuf->mnHeight,
343 pMaskBuf->mnScanlineSize);
345 else
347 // the alpha values need to be inverted for Cairo
348 // so big stupid copy and invert here
349 const int nImageSize = pMaskBuf->mnHeight * pMaskBuf->mnScanlineSize;
350 pAlphaBits.reset( new unsigned char[nImageSize] );
351 memcpy(pAlphaBits.get(), pMaskBuf->mpBits, nImageSize);
353 const sal_Int32 nBlackIndex = pMaskBuf->maPalette.GetBestIndex(BitmapColor(COL_BLACK));
354 if (nBlackIndex == 0)
356 // TODO: make upper layers use standard alpha
357 unsigned char* pDst = pAlphaBits.get();
358 for (int i = nImageSize; --i >= 0; ++pDst)
359 *pDst = ~*pDst;
362 mask = cairo_image_surface_create_for_data(pAlphaBits.get(),
363 CAIRO_FORMAT_A1,
364 pMaskBuf->mnWidth, pMaskBuf->mnHeight,
365 pMaskBuf->mnScanlineSize);
368 ~MaskHelper()
370 cairo_surface_destroy(mask);
372 cairo_surface_t* getMask()
374 return mask;
376 private:
377 cairo_surface_t *mask;
378 std::unique_ptr<unsigned char[]> pAlphaBits;
380 MaskHelper(const MaskHelper&) = delete;
381 MaskHelper& operator=(const MaskHelper&) = delete;
385 bool SvpSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap )
387 if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1)
389 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap alpha depth case: " << rAlphaBitmap.GetBitCount());
390 return false;
393 SourceHelper aSurface(rSourceBitmap);
394 cairo_surface_t* source = aSurface.getSurface();
395 if (!source)
397 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
398 return false;
401 MaskHelper aMask(rAlphaBitmap);
402 cairo_surface_t *mask = aMask.getMask();
403 if (!mask)
405 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
406 return false;
409 cairo_t* cr = getCairoContext(false);
410 clipRegion(cr);
412 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
414 basegfx::B2DRange extents = getClippedFillDamage(cr);
416 cairo_clip(cr);
418 cairo_pattern_t* maskpattern = cairo_pattern_create_for_surface(mask);
419 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
420 double fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth;
421 double fYScale = static_cast<double>(rTR.mnDestHeight)/rTR.mnSrcHeight;
422 cairo_scale(cr, fXScale, fYScale);
423 cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
425 //tdf#114117 when stretching a single pixel width/height source to fit an area
426 //set extend and filter to stretch it with simplest expected interpolation
427 if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
429 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
430 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
431 cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
432 cairo_pattern_set_extend(maskpattern, CAIRO_EXTEND_REPEAT);
433 cairo_pattern_set_filter(maskpattern, CAIRO_FILTER_NEAREST);
436 //this block is just "cairo_mask_surface", but we have to make it explicit
437 //because of the cairo_pattern_set_filter etc we may want applied
438 cairo_matrix_t matrix;
439 cairo_matrix_init_translate(&matrix, rTR.mnSrcX, rTR.mnSrcY);
440 cairo_pattern_set_matrix(maskpattern, &matrix);
441 cairo_mask(cr, maskpattern);
443 cairo_pattern_destroy(maskpattern);
445 releaseCairoContext(cr, false, extents);
447 return true;
450 bool SvpSalGraphics::drawTransformedBitmap(
451 const basegfx::B2DPoint& rNull,
452 const basegfx::B2DPoint& rX,
453 const basegfx::B2DPoint& rY,
454 const SalBitmap& rSourceBitmap,
455 const SalBitmap* pAlphaBitmap)
457 if (pAlphaBitmap && pAlphaBitmap->GetBitCount() != 8 && pAlphaBitmap->GetBitCount() != 1)
459 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap alpha depth case: " << pAlphaBitmap->GetBitCount());
460 return false;
463 SourceHelper aSurface(rSourceBitmap);
464 cairo_surface_t* source = aSurface.getSurface();
465 if (!source)
467 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
468 return false;
471 std::unique_ptr<MaskHelper> xMask;
472 cairo_surface_t *mask = nullptr;
473 if (pAlphaBitmap)
475 xMask.reset(new MaskHelper(*pAlphaBitmap));
476 mask = xMask->getMask();
477 if (!mask)
479 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
480 return false;
484 const Size aSize = rSourceBitmap.GetSize();
486 cairo_t* cr = getCairoContext(false);
487 clipRegion(cr);
489 // setup the image transformation
490 // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
491 const basegfx::B2DVector aXRel = rX - rNull;
492 const basegfx::B2DVector aYRel = rY - rNull;
493 cairo_matrix_t matrix;
494 cairo_matrix_init(&matrix,
495 aXRel.getX()/aSize.Width(), aXRel.getY()/aSize.Width(),
496 aYRel.getX()/aSize.Height(), aYRel.getY()/aSize.Height(),
497 rNull.getX(), rNull.getY());
499 cairo_transform(cr, &matrix);
501 cairo_rectangle(cr, 0, 0, aSize.Width(), aSize.Height());
502 basegfx::B2DRange extents = getClippedFillDamage(cr);
503 cairo_clip(cr);
505 cairo_set_source_surface(cr, source, 0, 0);
506 if (mask)
507 cairo_mask_surface(cr, mask, 0, 0);
508 else
509 cairo_paint(cr);
511 releaseCairoContext(cr, false, extents);
513 return true;
516 void SvpSalGraphics::clipRegion(cairo_t* cr, const vcl::Region& rClipRegion)
518 RectangleVector aRectangles;
519 if (!rClipRegion.IsEmpty())
521 rClipRegion.GetRegionRectangles(aRectangles);
523 if (!aRectangles.empty())
525 for (auto const& rectangle : aRectangles)
527 cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(), rectangle.GetHeight());
529 cairo_clip(cr);
533 void SvpSalGraphics::clipRegion(cairo_t* cr)
535 SvpSalGraphics::clipRegion(cr, m_aClipRegion);
538 bool SvpSalGraphics::drawAlphaRect(long nX, long nY, long nWidth, long nHeight, sal_uInt8 nTransparency)
540 const bool bHasFill(m_aFillColor != SALCOLOR_NONE);
541 const bool bHasLine(m_aLineColor != SALCOLOR_NONE);
543 if(!(bHasFill || bHasLine))
545 return true;
548 cairo_t* cr = getCairoContext(false);
549 clipRegion(cr);
551 const double fTransparency = nTransparency * (1.0/100);
553 // To make releaseCairoContext work, use empty extents
554 basegfx::B2DRange extents;
556 cairo_rectangle(cr, nX, nY, nWidth, nHeight);
558 if (bHasFill)
560 applyColor(cr, m_aFillColor, fTransparency);
562 // set FillDamage
563 extents = getClippedFillDamage(cr);
565 cairo_fill_preserve(cr);
568 if (bHasLine)
570 // PixelOffset used: Set PixelOffset as linear transformation
571 // Note: Was missing here - probably not by purpose (?)
572 cairo_matrix_t aMatrix;
573 cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
574 cairo_set_matrix(cr, &aMatrix);
576 applyColor(cr, m_aLineColor, fTransparency);
578 // expand with possible StrokeDamage
579 extents.expand(getClippedStrokeDamage(cr));
581 cairo_stroke_preserve(cr);
584 releaseCairoContext(cr, false, extents);
586 return true;
589 SvpSalGraphics::SvpSalGraphics()
590 : m_pSurface(nullptr)
591 , m_fScale(1.0)
592 , m_aLineColor(Color(0x00, 0x00, 0x00))
593 , m_aFillColor(Color(0xFF, 0xFF, 0XFF))
594 , m_ePaintMode(PaintMode::Over)
595 , m_aTextRenderImpl(*this)
597 bool bLOKActive = comphelper::LibreOfficeKit::isActive();
598 if (!initWidgetDrawBackends(bLOKActive))
600 if (bLOKActive)
601 m_pWidgetDraw.reset(new vcl::CustomWidgetDraw(*this));
605 SvpSalGraphics::~SvpSalGraphics()
609 void SvpSalGraphics::setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize)
611 m_pSurface = pSurface;
612 m_aFrameSize = rSize;
613 dl_cairo_surface_get_device_scale(pSurface, &m_fScale, nullptr);
614 ResetClipRegion();
617 void SvpSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY )
619 rDPIX = rDPIY = 96;
622 sal_uInt16 SvpSalGraphics::GetBitCount() const
624 if (cairo_surface_get_content(m_pSurface) != CAIRO_CONTENT_COLOR_ALPHA)
625 return 1;
626 return 32;
629 long SvpSalGraphics::GetGraphicsWidth() const
631 return m_pSurface ? m_aFrameSize.getX() : 0;
634 void SvpSalGraphics::ResetClipRegion()
636 m_aClipRegion.SetNull();
639 bool SvpSalGraphics::setClipRegion( const vcl::Region& i_rClip )
641 m_aClipRegion = i_rClip;
642 return true;
645 void SvpSalGraphics::SetLineColor()
647 m_aLineColor = SALCOLOR_NONE;
650 void SvpSalGraphics::SetLineColor( Color nColor )
652 m_aLineColor = nColor;
655 void SvpSalGraphics::SetFillColor()
657 m_aFillColor = SALCOLOR_NONE;
660 void SvpSalGraphics::SetFillColor( Color nColor )
662 m_aFillColor = nColor;
665 void SvpSalGraphics::SetXORMode(bool bSet, bool )
667 m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over;
670 void SvpSalGraphics::SetROPLineColor( SalROPColor nROPColor )
672 switch( nROPColor )
674 case SalROPColor::N0:
675 m_aLineColor = Color(0, 0, 0);
676 break;
677 case SalROPColor::N1:
678 m_aLineColor = Color(0xff, 0xff, 0xff);
679 break;
680 case SalROPColor::Invert:
681 m_aLineColor = Color(0xff, 0xff, 0xff);
682 break;
686 void SvpSalGraphics::SetROPFillColor( SalROPColor nROPColor )
688 switch( nROPColor )
690 case SalROPColor::N0:
691 m_aFillColor = Color(0, 0, 0);
692 break;
693 case SalROPColor::N1:
694 m_aFillColor = Color(0xff, 0xff, 0xff);
695 break;
696 case SalROPColor::Invert:
697 m_aFillColor = Color(0xff, 0xff, 0xff);
698 break;
702 void SvpSalGraphics::drawPixel( long nX, long nY )
704 if (m_aLineColor != SALCOLOR_NONE)
706 drawPixel(nX, nY, m_aLineColor);
710 void SvpSalGraphics::drawPixel( long nX, long nY, Color nColor )
712 Color aOrigFillColor = m_aFillColor;
713 Color aOrigLineColor = m_aLineColor;
715 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+1, nY+1));
716 m_aLineColor = SALCOLOR_NONE;
717 m_aFillColor = nColor;
719 drawPolyPolygon(
720 basegfx::B2DHomMatrix(),
721 basegfx::B2DPolyPolygon(aRect),
722 0.0);
724 m_aFillColor = aOrigFillColor;
725 m_aLineColor = aOrigLineColor;
728 void SvpSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight )
730 // because of the -1 hack we have to do fill and draw separately
731 Color aOrigFillColor = m_aFillColor;
732 Color aOrigLineColor = m_aLineColor;
733 m_aFillColor = SALCOLOR_NONE;
734 m_aLineColor = SALCOLOR_NONE;
736 if (aOrigFillColor != SALCOLOR_NONE)
738 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight));
739 m_aFillColor = aOrigFillColor;
741 drawPolyPolygon(
742 basegfx::B2DHomMatrix(),
743 basegfx::B2DPolyPolygon(aRect),
744 0.0);
746 m_aFillColor = SALCOLOR_NONE;
749 if (aOrigLineColor != SALCOLOR_NONE)
751 // need same -1 hack as X11SalGraphicsImpl::drawRect
752 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle( nX, nY, nX+nWidth-1, nY+nHeight-1));
753 m_aLineColor = aOrigLineColor;
755 drawPolyPolygon(
756 basegfx::B2DHomMatrix(),
757 basegfx::B2DPolyPolygon(aRect),
758 0.0);
760 m_aLineColor = SALCOLOR_NONE;
763 m_aFillColor = aOrigFillColor;
764 m_aLineColor = aOrigLineColor;
767 void SvpSalGraphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry)
769 basegfx::B2DPolygon aPoly;
770 aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
771 for (sal_uInt32 i = 1; i < nPoints; ++i)
772 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
773 aPoly.setClosed(false);
775 drawPolyLine(
776 basegfx::B2DHomMatrix(),
777 aPoly,
778 0.0,
779 basegfx::B2DVector(1.0, 1.0),
780 basegfx::B2DLineJoin::Miter,
781 css::drawing::LineCap_BUTT,
782 basegfx::deg2rad(15.0) /*default*/,
783 false);
786 void SvpSalGraphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
788 basegfx::B2DPolygon aPoly;
789 aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
790 for (sal_uInt32 i = 1; i < nPoints; ++i)
791 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
793 drawPolyPolygon(
794 basegfx::B2DHomMatrix(),
795 basegfx::B2DPolyPolygon(aPoly),
796 0.0);
799 void SvpSalGraphics::drawPolyPolygon(sal_uInt32 nPoly,
800 const sal_uInt32* pPointCounts,
801 PCONSTSALPOINT* pPtAry)
803 basegfx::B2DPolyPolygon aPolyPoly;
804 for(sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
806 sal_uInt32 nPoints = pPointCounts[nPolygon];
807 if (nPoints)
809 PCONSTSALPOINT pPoints = pPtAry[nPolygon];
810 basegfx::B2DPolygon aPoly;
811 aPoly.append( basegfx::B2DPoint(pPoints->mnX, pPoints->mnY), nPoints);
812 for (sal_uInt32 i = 1; i < nPoints; ++i)
813 aPoly.setB2DPoint(i, basegfx::B2DPoint( pPoints[i].mnX, pPoints[i].mnY));
815 aPolyPoly.append(aPoly);
819 drawPolyPolygon(
820 basegfx::B2DHomMatrix(),
821 aPolyPoly,
822 0.0);
825 static basegfx::B2DPoint impPixelSnap(
826 const basegfx::B2DPolygon& rPolygon,
827 const basegfx::B2DHomMatrix& rObjectToDevice,
828 basegfx::B2DHomMatrix& rObjectToDeviceInv,
829 sal_uInt32 nIndex)
831 const sal_uInt32 nCount(rPolygon.count());
833 // get the data
834 const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
835 const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex));
836 const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
837 const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount)));
839 // get the states
840 const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
841 const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
842 const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
843 const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
844 const bool bSnapX(bPrevVertical || bNextVertical);
845 const bool bSnapY(bPrevHorizontal || bNextHorizontal);
847 if(bSnapX || bSnapY)
849 basegfx::B2DPoint aSnappedPoint(
850 bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
851 bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
853 if(rObjectToDeviceInv.isIdentity())
855 rObjectToDeviceInv = rObjectToDevice;
856 rObjectToDeviceInv.invert();
859 aSnappedPoint *= rObjectToDeviceInv;
861 return aSnappedPoint;
864 return rPolygon.getB2DPoint(nIndex);
867 // Remove bClosePath: Checked that the already used mechanism for Win using
868 // Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace
869 // this.
870 // For PixelSnap we need the ObjectToDevice transformation here now. Tis is a
871 // special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in
872 // DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we
873 // need the ObjectToDevice transformation *without* that offset here to do the
874 // same. The LineDraw-Offset will be applied by the callers using a linear
875 // transformation for Cairo now
876 // For support of PixelSnapHairline we also need the ObjectToDevice transformation
877 // and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g.
878 // for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!)
879 static void AddPolygonToPath(
880 cairo_t* cr,
881 const basegfx::B2DPolygon& rPolygon,
882 const basegfx::B2DHomMatrix& rObjectToDevice,
883 bool bPixelSnap,
884 bool bPixelSnapHairline)
886 // short circuit if there is nothing to do
887 const sal_uInt32 nPointCount(rPolygon.count());
889 if(0 == nPointCount)
891 return;
894 const bool bHasCurves(rPolygon.areControlPointsUsed());
895 const bool bClosePath(rPolygon.isClosed());
896 const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity());
897 basegfx::B2DHomMatrix aObjectToDeviceInv;
898 basegfx::B2DPoint aLast;
900 for( sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++ )
902 int nClosedIdx = nPointIdx;
903 if( nPointIdx >= nPointCount )
905 // prepare to close last curve segment if needed
906 if( bClosePath && (nPointIdx == nPointCount) )
908 nClosedIdx = 0;
910 else
912 break;
916 basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(nClosedIdx));
918 if(bPixelSnap)
920 // snap device coordinates to full pixels
921 if(bObjectToDeviceUsed)
923 // go to DeviceCoordinates
924 aPoint *= rObjectToDevice;
927 // snap by rounding
928 aPoint.setX( basegfx::fround( aPoint.getX() ) );
929 aPoint.setY( basegfx::fround( aPoint.getY() ) );
931 if(bObjectToDeviceUsed)
933 if(aObjectToDeviceInv.isIdentity())
935 aObjectToDeviceInv = rObjectToDevice;
936 aObjectToDeviceInv.invert();
939 // go back to ObjectCoordinates
940 aPoint *= aObjectToDeviceInv;
944 if(bPixelSnapHairline)
946 // snap horizontal and vertical lines (mainly used in Chart for
947 // 'nicer' AAing)
948 aPoint = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx);
951 if( !nPointIdx )
953 // first point => just move there
954 cairo_move_to(cr, aPoint.getX(), aPoint.getY());
955 aLast = aPoint;
956 continue;
959 bool bPendingCurve(false);
961 if( bHasCurves )
963 bPendingCurve = rPolygon.isNextControlPointUsed( nPrevIdx );
964 bPendingCurve |= rPolygon.isPrevControlPointUsed( nClosedIdx );
967 if( !bPendingCurve ) // line segment
969 cairo_line_to(cr, aPoint.getX(), aPoint.getY());
971 else // cubic bezier segment
973 basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint( nPrevIdx );
974 basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint( nClosedIdx );
976 // tdf#99165 if the control points are 'empty', create the mathematical
977 // correct replacement ones to avoid problems with the graphical sub-system
978 // tdf#101026 The 1st attempt to create a mathematically correct replacement control
979 // vector was wrong. Best alternative is one as close as possible which means short.
980 if (aCP1.equal(aLast))
982 aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
985 if(aCP2.equal(aPoint))
987 aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005);
990 cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(),
991 aPoint.getX(), aPoint.getY());
994 aLast = aPoint;
997 if( bClosePath )
999 cairo_close_path(cr);
1003 void SvpSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
1005 basegfx::B2DPolygon aPoly;
1007 // PixelOffset used: To not mix with possible PixelSnap, cannot do
1008 // directly on coordinates as tried before - despite being already 'snapped'
1009 // due to being integer. If it would be directly added here, it would be
1010 // 'snapped' again when !getAntiAliasB2DDraw(), losing the (0.5, 0.5) offset
1011 aPoly.append(basegfx::B2DPoint(nX1, nY1));
1012 aPoly.append(basegfx::B2DPoint(nX2, nY2));
1014 cairo_t* cr = getCairoContext(false);
1015 clipRegion(cr);
1017 // PixelOffset used: Set PixelOffset as linear transformation
1018 cairo_matrix_t aMatrix;
1019 cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
1020 cairo_set_matrix(cr, &aMatrix);
1022 AddPolygonToPath(
1024 aPoly,
1025 basegfx::B2DHomMatrix(),
1026 !getAntiAliasB2DDraw(),
1027 false);
1029 applyColor(cr, m_aLineColor);
1031 basegfx::B2DRange extents = getClippedStrokeDamage(cr);
1033 cairo_stroke(cr);
1035 releaseCairoContext(cr, false, extents);
1038 class SystemDependentData_CairoPath : public basegfx::SystemDependentData
1040 private:
1041 // the path data itself
1042 cairo_path_t* mpCairoPath;
1044 // all other values the path data is based on and
1045 // need to be compared with to check for data validity
1046 bool mbNoJoin;
1047 bool mbAntiAliasB2DDraw;
1049 public:
1050 SystemDependentData_CairoPath(
1051 basegfx::SystemDependentDataManager& rSystemDependentDataManager,
1052 cairo_path_t* pCairoPath,
1053 bool bNoJoin,
1054 bool bAntiAliasB2DDraw);
1055 virtual ~SystemDependentData_CairoPath() override;
1057 cairo_path_t* getCairoPath() { return mpCairoPath; }
1058 bool getNoJoin() const { return mbNoJoin; }
1059 bool getAntiAliasB2DDraw() const { return mbAntiAliasB2DDraw; }
1061 virtual sal_Int64 estimateUsageInBytes() const override;
1064 SystemDependentData_CairoPath::SystemDependentData_CairoPath(
1065 basegfx::SystemDependentDataManager& rSystemDependentDataManager,
1066 cairo_path_t* pCairoPath,
1067 bool bNoJoin,
1068 bool bAntiAliasB2DDraw)
1069 : basegfx::SystemDependentData(rSystemDependentDataManager),
1070 mpCairoPath(pCairoPath),
1071 mbNoJoin(bNoJoin),
1072 mbAntiAliasB2DDraw(bAntiAliasB2DDraw)
1076 SystemDependentData_CairoPath::~SystemDependentData_CairoPath()
1078 if(nullptr != mpCairoPath)
1080 cairo_path_destroy(mpCairoPath);
1081 mpCairoPath = nullptr;
1085 sal_Int64 SystemDependentData_CairoPath::estimateUsageInBytes() const
1087 sal_Int64 nRetval(0);
1089 if(nullptr != mpCairoPath)
1091 // per node
1092 // - num_data incarnations of
1093 // - sizeof(cairo_path_data_t) which is a union of defines and point data
1094 // thus may 2 x sizeof(double)
1095 nRetval = mpCairoPath->num_data * sizeof(cairo_path_data_t);
1098 return nRetval;
1101 bool SvpSalGraphics::drawPolyLine(
1102 const basegfx::B2DHomMatrix& rObjectToDevice,
1103 const basegfx::B2DPolygon& rPolyLine,
1104 double fTransparency,
1105 const basegfx::B2DVector& rLineWidths,
1106 basegfx::B2DLineJoin eLineJoin,
1107 css::drawing::LineCap eLineCap,
1108 double fMiterMinimumAngle,
1109 bool bPixelSnapHairline)
1111 // short circuit if there is nothing to do
1112 if(0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
1114 return true;
1117 // Wrap call to static version of ::drawPolyLine by
1118 // preparing/getting some local data and parameters
1119 // due to usage in vcl/unx/generic/gdi/salgdi.cxx.
1120 // This is mainly about extended handling of extents
1121 // and the way destruction of CairoContext is handled
1122 // due to current XOR stuff
1123 cairo_t* cr = getCairoContext(false);
1124 basegfx::B2DRange aExtents;
1125 clipRegion(cr);
1127 bool bRetval(
1128 drawPolyLine(
1130 &aExtents,
1131 m_aLineColor,
1132 getAntiAliasB2DDraw(),
1133 rObjectToDevice,
1134 rPolyLine,
1135 fTransparency,
1136 rLineWidths,
1137 eLineJoin,
1138 eLineCap,
1139 fMiterMinimumAngle,
1140 bPixelSnapHairline));
1142 // if transformation has been applied, transform also extents (ranges)
1143 // of damage so they can be correctly redrawn
1144 aExtents.transform(rObjectToDevice);
1145 releaseCairoContext(cr, false, aExtents);
1147 return bRetval;
1150 bool SvpSalGraphics::drawPolyLine(
1151 cairo_t* cr,
1152 basegfx::B2DRange* pExtents,
1153 const Color& rLineColor,
1154 bool bAntiAliasB2DDraw,
1155 const basegfx::B2DHomMatrix& rObjectToDevice,
1156 const basegfx::B2DPolygon& rPolyLine,
1157 double fTransparency,
1158 const basegfx::B2DVector& rLineWidths,
1159 basegfx::B2DLineJoin eLineJoin,
1160 css::drawing::LineCap eLineCap,
1161 double fMiterMinimumAngle,
1162 bool bPixelSnapHairline)
1164 // short circuit if there is nothing to do
1165 if(0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
1167 return true;
1170 // need to check/handle LineWidth when ObjectToDevice transformation is used
1171 basegfx::B2DVector aLineWidths(rLineWidths);
1172 const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
1173 const basegfx::B2DVector aDeviceLineWidths(bObjectToDeviceIsIdentity ? rLineWidths : rObjectToDevice * rLineWidths);
1174 const bool bCorrectLineWidth(!bObjectToDeviceIsIdentity && aDeviceLineWidths.getX() < 1.0 && aLineWidths.getX() >= 1.0);
1176 // on-demand inverse of ObjectToDevice transformation
1177 basegfx::B2DHomMatrix aObjectToDeviceInv;
1179 if(bCorrectLineWidth)
1181 if(aObjectToDeviceInv.isIdentity())
1183 aObjectToDeviceInv = rObjectToDevice;
1184 aObjectToDeviceInv.invert();
1187 // calculate-back logical LineWidth for a hairline
1188 aLineWidths = aObjectToDeviceInv * basegfx::B2DVector(1.0, 1.0);
1191 // PixelOffset used: Need to reflect in linear transformation
1192 cairo_matrix_t aMatrix;
1194 if(bObjectToDeviceIsIdentity)
1196 // Set PixelOffset as requested
1197 cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
1199 else
1201 // Prepare ObjectToDevice transformation. Take PixelOffset for Lines into
1202 // account: Multiply from left to act in DeviceCoordinates
1203 const basegfx::B2DHomMatrix aCombined(
1204 basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5) * rObjectToDevice);
1205 cairo_matrix_init(
1206 &aMatrix,
1207 aCombined.get( 0, 0 ),
1208 aCombined.get( 1, 0 ),
1209 aCombined.get( 0, 1 ),
1210 aCombined.get( 1, 1 ),
1211 aCombined.get( 0, 2 ),
1212 aCombined.get( 1, 2 ));
1215 // set linear transformation
1216 cairo_set_matrix(cr, &aMatrix);
1218 const bool bNoJoin((basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(aLineWidths.getX(), 0.0)));
1220 // setup line attributes
1221 cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
1222 switch (eLineJoin)
1224 case basegfx::B2DLineJoin::Bevel:
1225 eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
1226 break;
1227 case basegfx::B2DLineJoin::Round:
1228 eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
1229 break;
1230 case basegfx::B2DLineJoin::NONE:
1231 case basegfx::B2DLineJoin::Miter:
1232 eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
1233 break;
1236 // convert miter minimum angle to miter limit
1237 double fMiterLimit = 1.0 / sin( fMiterMinimumAngle / 2.0);
1239 // setup cap attribute
1240 cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
1242 switch (eLineCap)
1244 default: // css::drawing::LineCap_BUTT:
1246 eCairoLineCap = CAIRO_LINE_CAP_BUTT;
1247 break;
1249 case css::drawing::LineCap_ROUND:
1251 eCairoLineCap = CAIRO_LINE_CAP_ROUND;
1252 break;
1254 case css::drawing::LineCap_SQUARE:
1256 eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
1257 break;
1261 cairo_set_source_rgba(
1263 rLineColor.GetRed()/255.0,
1264 rLineColor.GetGreen()/255.0,
1265 rLineColor.GetBlue()/255.0,
1266 1.0-fTransparency);
1268 cairo_set_line_join(cr, eCairoLineJoin);
1269 cairo_set_line_cap(cr, eCairoLineCap);
1270 cairo_set_line_width(cr, aLineWidths.getX());
1271 cairo_set_miter_limit(cr, fMiterLimit);
1273 // try to access buffered data
1274 std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
1275 rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>());
1277 if(pSystemDependentData_CairoPath)
1279 // check data validity
1280 if(nullptr == pSystemDependentData_CairoPath->getCairoPath()
1281 || pSystemDependentData_CairoPath->getNoJoin() != bNoJoin
1282 || pSystemDependentData_CairoPath->getAntiAliasB2DDraw() != bAntiAliasB2DDraw)
1284 // data invalid, forget
1285 pSystemDependentData_CairoPath.reset();
1289 if(pSystemDependentData_CairoPath)
1291 // re-use data
1292 cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
1294 else
1296 // create data
1297 if (!bNoJoin)
1299 // PixelOffset now reflected in linear transformation used
1300 AddPolygonToPath(
1302 rPolyLine,
1303 rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
1304 !bAntiAliasB2DDraw,
1305 bPixelSnapHairline);
1307 else
1309 const sal_uInt32 nPointCount(rPolyLine.count());
1310 const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1);
1311 basegfx::B2DPolygon aEdge;
1313 aEdge.append(rPolyLine.getB2DPoint(0));
1314 aEdge.append(basegfx::B2DPoint(0.0, 0.0));
1316 for (sal_uInt32 i(0); i < nEdgeCount; i++)
1318 const sal_uInt32 nNextIndex((i + 1) % nPointCount);
1319 aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex));
1320 aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i));
1321 aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex));
1323 // PixelOffset now reflected in linear transformation used
1324 AddPolygonToPath(
1326 aEdge,
1327 rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
1328 !bAntiAliasB2DDraw,
1329 bPixelSnapHairline);
1331 // prepare next step
1332 aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
1336 // copy and add to buffering mechanism
1337 pSystemDependentData_CairoPath = rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
1338 ImplGetSystemDependentDataManager(),
1339 cairo_copy_path(cr),
1340 bNoJoin,
1341 bAntiAliasB2DDraw);
1344 // extract extents
1345 if (pExtents)
1346 *pExtents = getClippedStrokeDamage(cr);
1348 // draw and consume
1349 cairo_stroke(cr);
1351 return true;
1354 bool SvpSalGraphics::drawPolyLineBezier( sal_uInt32,
1355 const SalPoint*,
1356 const PolyFlags* )
1358 SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyLineBezier case");
1359 return false;
1362 bool SvpSalGraphics::drawPolygonBezier( sal_uInt32,
1363 const SalPoint*,
1364 const PolyFlags* )
1366 SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolygonBezier case");
1367 return false;
1370 bool SvpSalGraphics::drawPolyPolygonBezier( sal_uInt32,
1371 const sal_uInt32*,
1372 const SalPoint* const*,
1373 const PolyFlags* const* )
1375 SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyPolygonBezier case");
1376 return false;
1379 bool SvpSalGraphics::drawPolyPolygon(
1380 const basegfx::B2DHomMatrix& rObjectToDevice,
1381 const basegfx::B2DPolyPolygon& rPolyPolygon,
1382 double fTransparency)
1384 const bool bHasFill(m_aFillColor != SALCOLOR_NONE);
1385 const bool bHasLine(m_aLineColor != SALCOLOR_NONE);
1387 if(0 == rPolyPolygon.count() || !(bHasFill || bHasLine) || fTransparency < 0.0 || fTransparency >= 1.0)
1389 return true;
1392 cairo_t* cr = getCairoContext(true);
1393 clipRegion(cr);
1395 // Set full (Object-to-Device) transformation - if used
1396 if(!rObjectToDevice.isIdentity())
1398 cairo_matrix_t aMatrix;
1400 cairo_matrix_init(
1401 &aMatrix,
1402 rObjectToDevice.get( 0, 0 ),
1403 rObjectToDevice.get( 1, 0 ),
1404 rObjectToDevice.get( 0, 1 ),
1405 rObjectToDevice.get( 1, 1 ),
1406 rObjectToDevice.get( 0, 2 ),
1407 rObjectToDevice.get( 1, 2 ));
1408 cairo_set_matrix(cr, &aMatrix);
1411 // try to access buffered data
1412 std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
1413 rPolyPolygon.getSystemDependentData<SystemDependentData_CairoPath>());
1415 if(pSystemDependentData_CairoPath)
1417 // re-use data
1418 cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
1420 else
1422 // create data
1423 for (const auto & rPoly : rPolyPolygon)
1425 // PixelOffset used: Was dependent of 'm_aLineColor != SALCOLOR_NONE'
1426 // Adapt setupPolyPolygon-users to set a linear transformation to achieve PixelOffset
1427 AddPolygonToPath(
1429 rPoly,
1430 rObjectToDevice,
1431 !getAntiAliasB2DDraw(),
1432 false);
1435 // copy and add to buffering mechanism
1436 // for decisions how/what to buffer, see Note in WinSalGraphicsImpl::drawPolyPolygon
1437 pSystemDependentData_CairoPath = rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
1438 ImplGetSystemDependentDataManager(),
1439 cairo_copy_path(cr),
1440 false,
1441 false);
1444 // To make releaseCairoContext work, use empty extents
1445 basegfx::B2DRange extents;
1447 if (bHasFill)
1449 applyColor(cr, m_aFillColor, fTransparency);
1450 // Get FillDamage (will be extended for LineDamage below)
1451 extents = getClippedFillDamage(cr);
1453 cairo_fill_preserve(cr);
1456 if (bHasLine)
1458 // PixelOffset used: Set PixelOffset as linear transformation
1459 cairo_matrix_t aMatrix;
1460 cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
1461 cairo_set_matrix(cr, &aMatrix);
1463 applyColor(cr, m_aLineColor, fTransparency);
1465 // expand with possible StrokeDamage
1466 extents.expand(getClippedStrokeDamage(cr));
1468 cairo_stroke_preserve(cr);
1471 // if transformation has been applied, transform also extents (ranges)
1472 // of damage so they can be correctly redrawn
1473 extents.transform(rObjectToDevice);
1474 releaseCairoContext(cr, true, extents);
1476 return true;
1479 void SvpSalGraphics::applyColor(cairo_t *cr, Color aColor, double fTransparency)
1481 if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
1483 cairo_set_source_rgba(cr, aColor.GetRed()/255.0,
1484 aColor.GetGreen()/255.0,
1485 aColor.GetBlue()/255.0,
1486 1.0 - fTransparency);
1488 else
1490 double fSet = aColor == COL_BLACK ? 1.0 : 0.0;
1491 cairo_set_source_rgba(cr, 1, 1, 1, fSet);
1492 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1496 void SvpSalGraphics::copyArea( long nDestX,
1497 long nDestY,
1498 long nSrcX,
1499 long nSrcY,
1500 long nSrcWidth,
1501 long nSrcHeight,
1502 bool /*bWindowInvalidate*/ )
1504 SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
1505 copyBits(aTR, this);
1508 static basegfx::B2DRange renderWithOperator(cairo_t* cr, const SalTwoRect& rTR,
1509 cairo_surface_t* source, cairo_operator_t eOperator = CAIRO_OPERATOR_SOURCE)
1511 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1513 basegfx::B2DRange extents = getClippedFillDamage(cr);
1515 cairo_clip(cr);
1517 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1518 double fXScale = 1.0f;
1519 double fYScale = 1.0f;
1520 if (rTR.mnSrcWidth != 0 && rTR.mnSrcHeight != 0) {
1521 fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth;
1522 fYScale = static_cast<double>(rTR.mnDestHeight)/rTR.mnSrcHeight;
1523 cairo_scale(cr, fXScale, fYScale);
1526 cairo_save(cr);
1527 cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
1528 if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
1530 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1531 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
1532 cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
1534 cairo_set_operator(cr, eOperator);
1535 cairo_paint(cr);
1536 cairo_restore(cr);
1538 return extents;
1541 static basegfx::B2DRange renderSource(cairo_t* cr, const SalTwoRect& rTR,
1542 cairo_surface_t* source)
1544 return renderWithOperator(cr, rTR, source, CAIRO_OPERATOR_SOURCE);
1547 void SvpSalGraphics::copyWithOperator( const SalTwoRect& rTR, cairo_surface_t* source,
1548 cairo_operator_t eOp )
1550 cairo_t* cr = getCairoContext(false);
1551 clipRegion(cr);
1553 basegfx::B2DRange extents = renderWithOperator(cr, rTR, source, eOp);
1555 releaseCairoContext(cr, false, extents);
1558 void SvpSalGraphics::copySource( const SalTwoRect& rTR, cairo_surface_t* source )
1560 copyWithOperator(rTR, source, CAIRO_OPERATOR_SOURCE);
1563 void SvpSalGraphics::copyBits( const SalTwoRect& rTR,
1564 SalGraphics* pSrcGraphics )
1566 SalTwoRect aTR(rTR);
1568 SvpSalGraphics* pSrc = pSrcGraphics ?
1569 static_cast<SvpSalGraphics*>(pSrcGraphics) : this;
1571 cairo_surface_t* source = pSrc->m_pSurface;
1573 cairo_surface_t *pCopy = nullptr;
1574 if (pSrc == this)
1576 //self copy is a problem, so dup source in that case
1577 pCopy = cairo_surface_create_similar(source,
1578 cairo_surface_get_content(m_pSurface),
1579 aTR.mnSrcWidth * m_fScale,
1580 aTR.mnSrcHeight * m_fScale);
1581 dl_cairo_surface_set_device_scale(pCopy, m_fScale, m_fScale);
1582 cairo_t* cr = cairo_create(pCopy);
1583 cairo_set_source_surface(cr, source, -aTR.mnSrcX, -aTR.mnSrcY);
1584 cairo_rectangle(cr, 0, 0, aTR.mnSrcWidth, aTR.mnSrcHeight);
1585 cairo_fill(cr);
1586 cairo_destroy(cr);
1588 source = pCopy;
1590 aTR.mnSrcX = 0;
1591 aTR.mnSrcY = 0;
1594 copySource(aTR, source);
1596 if (pCopy)
1597 cairo_surface_destroy(pCopy);
1600 void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap)
1602 SourceHelper aSurface(rSourceBitmap);
1603 cairo_surface_t* source = aSurface.getSurface();
1604 copyWithOperator(rTR, source, CAIRO_OPERATOR_OVER);
1607 void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const BitmapBuffer* pBuffer, cairo_operator_t eOp)
1609 cairo_surface_t* source = createCairoSurface( pBuffer );
1610 copyWithOperator(rTR, source, eOp);
1611 cairo_surface_destroy(source);
1614 void SvpSalGraphics::drawBitmap( const SalTwoRect& rTR,
1615 const SalBitmap& rSourceBitmap,
1616 const SalBitmap& rTransparentBitmap )
1618 drawAlphaBitmap(rTR, rSourceBitmap, rTransparentBitmap);
1621 void SvpSalGraphics::drawMask( const SalTwoRect& rTR,
1622 const SalBitmap& rSalBitmap,
1623 Color nMaskColor )
1625 /** creates an image from the given rectangle, replacing all black pixels
1626 * with nMaskColor and make all other full transparent */
1627 SourceHelper aSurface(rSalBitmap, true); // The mask is argb32
1628 if (!aSurface.getSurface())
1630 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawMask case");
1631 return;
1633 sal_Int32 nStride;
1634 unsigned char *mask_data = aSurface.getBits(nStride);
1635 vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
1636 for (long y = rTR.mnSrcY ; y < rTR.mnSrcY + rTR.mnSrcHeight; ++y)
1638 unsigned char *row = mask_data + (nStride*y);
1639 unsigned char *data = row + (rTR.mnSrcX * 4);
1640 for (long x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x)
1642 sal_uInt8 a = data[SVP_CAIRO_ALPHA];
1643 sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]];
1644 sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]];
1645 sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]];
1646 if (r == 0 && g == 0 && b == 0)
1648 data[0] = nMaskColor.GetBlue();
1649 data[1] = nMaskColor.GetGreen();
1650 data[2] = nMaskColor.GetRed();
1651 data[3] = 0xff;
1653 else
1655 data[0] = 0;
1656 data[1] = 0;
1657 data[2] = 0;
1658 data[3] = 0;
1660 data+=4;
1663 aSurface.mark_dirty();
1665 cairo_t* cr = getCairoContext(false);
1666 clipRegion(cr);
1668 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1670 basegfx::B2DRange extents = getClippedFillDamage(cr);
1672 cairo_clip(cr);
1674 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1675 double fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth;
1676 double fYScale = static_cast<double>(rTR.mnDestHeight)/rTR.mnSrcHeight;
1677 cairo_scale(cr, fXScale, fYScale);
1678 cairo_set_source_surface(cr, aSurface.getSurface(), -rTR.mnSrcX, -rTR.mnSrcY);
1679 if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
1681 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1682 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
1683 cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
1685 cairo_paint(cr);
1687 releaseCairoContext(cr, false, extents);
1690 std::shared_ptr<SalBitmap> SvpSalGraphics::getBitmap( long nX, long nY, long nWidth, long nHeight )
1692 std::shared_ptr<SvpSalBitmap> pBitmap = std::make_shared<SvpSalBitmap>();
1693 BitmapPalette aPal;
1694 if (GetBitCount() == 1)
1696 aPal.SetEntryCount(2);
1697 aPal[0] = COL_BLACK;
1698 aPal[1] = COL_WHITE;
1701 if (!pBitmap->Create(Size(nWidth, nHeight), GetBitCount(), aPal))
1703 SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create bitmap");
1704 return nullptr;
1707 cairo_surface_t* target = SvpSalGraphics::createCairoSurface(pBitmap->GetBuffer());
1708 if (!target)
1710 SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create cairo surface");
1711 return nullptr;
1713 cairo_t* cr = cairo_create(target);
1715 SalTwoRect aTR(nX, nY, nWidth, nHeight, 0, 0, nWidth, nHeight);
1716 renderSource(cr, aTR, m_pSurface);
1718 cairo_destroy(cr);
1719 cairo_surface_destroy(target);
1721 Toggle1BitTransparency(*pBitmap->GetBuffer());
1723 return pBitmap;
1726 Color SvpSalGraphics::getPixel( long nX, long nY )
1728 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
1729 cairo_surface_t *target = cairo_surface_create_similar_image(m_pSurface, CAIRO_FORMAT_ARGB32, 1, 1);
1730 #else
1731 cairo_surface_t *target = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
1732 #endif
1734 cairo_t* cr = cairo_create(target);
1736 cairo_rectangle(cr, 0, 0, 1, 1);
1737 cairo_set_source_surface(cr, m_pSurface, -nX, -nY);
1738 cairo_paint(cr);
1739 cairo_destroy(cr);
1741 cairo_surface_flush(target);
1742 vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
1743 unsigned char *data = cairo_image_surface_get_data(target);
1744 sal_uInt8 a = data[SVP_CAIRO_ALPHA];
1745 sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]];
1746 sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]];
1747 sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]];
1748 Color aColor(0xFF - a, r, g, b);
1749 cairo_surface_destroy(target);
1751 return aColor;
1754 namespace
1756 cairo_pattern_t * create_stipple()
1758 static unsigned char data[16] = { 0xFF, 0xFF, 0x00, 0x00,
1759 0xFF, 0xFF, 0x00, 0x00,
1760 0x00, 0x00, 0xFF, 0xFF,
1761 0x00, 0x00, 0xFF, 0xFF };
1762 cairo_surface_t* surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_A8, 4, 4, 4);
1763 cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface);
1764 cairo_surface_destroy(surface);
1765 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
1766 cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
1767 return pattern;
1771 void SvpSalGraphics::invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags)
1773 cairo_t* cr = getCairoContext(false);
1774 clipRegion(cr);
1776 // To make releaseCairoContext work, use empty extents
1777 basegfx::B2DRange extents;
1779 AddPolygonToPath(
1781 rPoly,
1782 basegfx::B2DHomMatrix(),
1783 !getAntiAliasB2DDraw(),
1784 false);
1786 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
1788 if (cairo_version() >= CAIRO_VERSION_ENCODE(1, 10, 0))
1790 cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
1792 else
1794 SAL_WARN("vcl.gdi", "SvpSalGraphics::invert, archaic cairo");
1797 if (nFlags & SalInvert::TrackFrame)
1799 cairo_set_line_width(cr, 2.0);
1800 const double dashLengths[2] = { 4.0, 4.0 };
1801 cairo_set_dash(cr, dashLengths, 2, 0);
1803 extents = getClippedStrokeDamage(cr);
1804 //see tdf#106577 under wayland, some pixel droppings seen, maybe we're
1805 //out by one somewhere, or cairo_stroke_extents is confused by
1806 //dashes/line width
1807 if(!extents.isEmpty())
1809 extents.grow(1);
1812 cairo_stroke(cr);
1814 else
1816 extents = getClippedFillDamage(cr);
1818 cairo_clip(cr);
1820 if (nFlags & SalInvert::N50)
1822 cairo_pattern_t *pattern = create_stipple();
1823 cairo_surface_t* surface = cairo_surface_create_similar(m_pSurface,
1824 cairo_surface_get_content(m_pSurface),
1825 extents.getWidth() * m_fScale,
1826 extents.getHeight() * m_fScale);
1828 dl_cairo_surface_set_device_scale(surface, m_fScale, m_fScale);
1829 cairo_t* stipple_cr = cairo_create(surface);
1830 cairo_set_source_rgb(stipple_cr, 1.0, 1.0, 1.0);
1831 cairo_mask(stipple_cr, pattern);
1832 cairo_pattern_destroy(pattern);
1833 cairo_destroy(stipple_cr);
1834 cairo_mask_surface(cr, surface, extents.getMinX(), extents.getMinY());
1835 cairo_surface_destroy(surface);
1837 else
1839 cairo_paint(cr);
1843 releaseCairoContext(cr, false, extents);
1846 void SvpSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags )
1848 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight));
1850 invert(aRect, nFlags);
1853 void SvpSalGraphics::invert(sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags)
1855 basegfx::B2DPolygon aPoly;
1856 aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
1857 for (sal_uInt32 i = 1; i < nPoints; ++i)
1858 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
1859 aPoly.setClosed(true);
1861 invert(aPoly, nFlags);
1864 bool SvpSalGraphics::drawEPS( long, long, long, long, void*, sal_uInt32 )
1866 return false;
1869 /* Widget drawing */
1871 bool SvpSalGraphics::IsNativeControlSupported(ControlType eType, ControlPart ePart)
1873 if (hasWidgetDraw())
1874 return m_pWidgetDraw->isNativeControlSupported(eType, ePart);
1876 return false;
1879 bool SvpSalGraphics::hitTestNativeControl(ControlType eType, ControlPart ePart,
1880 const tools::Rectangle& rBoundingControlRegion,
1881 const Point& rPosition, bool& rIsInside)
1883 if (hasWidgetDraw())
1885 return m_pWidgetDraw->hitTestNativeControl(eType, ePart, rBoundingControlRegion, rPosition, rIsInside);
1888 return false;
1891 bool SvpSalGraphics::drawNativeControl(ControlType eType, ControlPart ePart,
1892 const tools::Rectangle& rControlRegion,
1893 ControlState eState, const ImplControlValue& aValue,
1894 const OUString& aCaptions)
1896 if (hasWidgetDraw())
1898 bool bReturn = m_pWidgetDraw->drawNativeControl(eType, ePart, rControlRegion,
1899 eState, aValue, aCaptions);
1900 return bReturn;
1903 return false;
1906 bool SvpSalGraphics::getNativeControlRegion(ControlType eType, ControlPart ePart,
1907 const tools::Rectangle& rBoundingControlRegion,
1908 ControlState eState,
1909 const ImplControlValue& aValue,
1910 const OUString& aCaption,
1911 tools::Rectangle& rNativeBoundingRegion,
1912 tools::Rectangle& rNativeContentRegion)
1914 if (hasWidgetDraw())
1916 return m_pWidgetDraw->getNativeControlRegion(eType, ePart, rBoundingControlRegion,
1917 eState, aValue, aCaption,
1918 rNativeBoundingRegion, rNativeContentRegion);
1921 return false;
1924 void SvpSalGraphics::updateSettings(AllSettings& rSettings)
1926 if (hasWidgetDraw())
1928 m_pWidgetDraw->updateSettings(rSettings);
1932 namespace
1934 bool isCairoCompatible(const BitmapBuffer* pBuffer)
1936 if (!pBuffer)
1937 return false;
1939 // We use Cairo that supports 24-bit RGB.
1940 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
1941 if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 24 && pBuffer->mnBitCount != 1)
1942 #else
1943 if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 1)
1944 #endif
1945 return false;
1947 cairo_format_t nFormat = getCairoFormat(*pBuffer);
1948 return (cairo_format_stride_for_width(nFormat, pBuffer->mnWidth) == pBuffer->mnScanlineSize);
1952 cairo_surface_t* SvpSalGraphics::createCairoSurface(const BitmapBuffer *pBuffer)
1954 if (!isCairoCompatible(pBuffer))
1955 return nullptr;
1957 cairo_format_t nFormat = getCairoFormat(*pBuffer);
1958 cairo_surface_t *target =
1959 cairo_image_surface_create_for_data(pBuffer->mpBits,
1960 nFormat,
1961 pBuffer->mnWidth, pBuffer->mnHeight,
1962 pBuffer->mnScanlineSize);
1963 if (cairo_surface_status(target) != CAIRO_STATUS_SUCCESS)
1965 cairo_surface_destroy(target);
1966 return nullptr;
1968 return target;
1971 cairo_t* SvpSalGraphics::createTmpCompatibleCairoContext() const
1973 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
1974 cairo_surface_t *target = cairo_surface_create_similar_image(m_pSurface,
1975 #else
1976 cairo_surface_t *target = cairo_image_surface_create(
1977 #endif
1978 CAIRO_FORMAT_ARGB32,
1979 m_aFrameSize.getX() * m_fScale,
1980 m_aFrameSize.getY() * m_fScale);
1982 dl_cairo_surface_set_device_scale(target, m_fScale, m_fScale);
1984 return cairo_create(target);
1987 cairo_t* SvpSalGraphics::getCairoContext(bool bXorModeAllowed) const
1989 cairo_t* cr;
1990 if (m_ePaintMode == PaintMode::Xor && bXorModeAllowed)
1991 cr = createTmpCompatibleCairoContext();
1992 else
1993 cr = cairo_create(m_pSurface);
1994 cairo_set_line_width(cr, 1);
1995 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1996 cairo_set_antialias(cr, getAntiAliasB2DDraw() ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
1997 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1999 // ensure no linear transformation and no PathInfo in local cairo_path_t
2000 cairo_identity_matrix(cr);
2001 cairo_new_path(cr);
2003 return cr;
2006 cairo_user_data_key_t* SvpSalGraphics::getDamageKey()
2008 static cairo_user_data_key_t aDamageKey;
2009 return &aDamageKey;
2012 void SvpSalGraphics::releaseCairoContext(cairo_t* cr, bool bXorModeAllowed, const basegfx::B2DRange& rExtents) const
2014 const bool bXoring = (m_ePaintMode == PaintMode::Xor && bXorModeAllowed);
2016 if (rExtents.isEmpty())
2018 //nothing changed, return early
2019 if (bXoring)
2021 cairo_surface_t* surface = cairo_get_target(cr);
2022 cairo_surface_destroy(surface);
2024 cairo_destroy(cr);
2025 return;
2028 sal_Int32 nExtentsLeft(rExtents.getMinX()), nExtentsTop(rExtents.getMinY());
2029 sal_Int32 nExtentsRight(rExtents.getMaxX()), nExtentsBottom(rExtents.getMaxY());
2030 sal_Int32 nWidth = m_aFrameSize.getX();
2031 sal_Int32 nHeight = m_aFrameSize.getY();
2032 nExtentsLeft = std::max<sal_Int32>(nExtentsLeft, 0);
2033 nExtentsTop = std::max<sal_Int32>(nExtentsTop, 0);
2034 nExtentsRight = std::min<sal_Int32>(nExtentsRight, nWidth);
2035 nExtentsBottom = std::min<sal_Int32>(nExtentsBottom, nHeight);
2037 cairo_surface_t* surface = cairo_get_target(cr);
2038 cairo_surface_flush(surface);
2040 //For the most part we avoid the use of XOR these days, but there
2041 //are some edge cases where legacy stuff still supports it, so
2042 //emulate it (slowly) here.
2043 if (bXoring)
2045 cairo_surface_t* target_surface = m_pSurface;
2046 if (cairo_surface_get_type(target_surface) != CAIRO_SURFACE_TYPE_IMAGE)
2048 //in the unlikely case we can't use m_pSurface directly, copy contents
2049 //to another temp image surface
2050 cairo_t* copycr = createTmpCompatibleCairoContext();
2051 cairo_rectangle(copycr, nExtentsLeft, nExtentsTop,
2052 nExtentsRight - nExtentsLeft,
2053 nExtentsBottom - nExtentsTop);
2054 cairo_set_source_surface(copycr, m_pSurface, 0, 0);
2055 cairo_paint(copycr);
2056 target_surface = cairo_get_target(copycr);
2057 cairo_destroy(copycr);
2060 cairo_surface_flush(target_surface);
2061 unsigned char *target_surface_data = cairo_image_surface_get_data(target_surface);
2062 unsigned char *xor_surface_data = cairo_image_surface_get_data(surface);
2064 cairo_format_t nFormat = cairo_image_surface_get_format(target_surface);
2065 assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here");
2066 sal_Int32 nStride = cairo_format_stride_for_width(nFormat, nWidth * m_fScale);
2067 sal_Int32 nUnscaledExtentsLeft = nExtentsLeft * m_fScale;
2068 sal_Int32 nUnscaledExtentsRight = nExtentsRight * m_fScale;
2069 sal_Int32 nUnscaledExtentsTop = nExtentsTop * m_fScale;
2070 sal_Int32 nUnscaledExtentsBottom = nExtentsBottom * m_fScale;
2071 vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
2072 vcl::bitmap::lookup_table premultiply_table = vcl::bitmap::get_premultiply_table();
2073 for (sal_Int32 y = nUnscaledExtentsTop; y < nUnscaledExtentsBottom; ++y)
2075 unsigned char *true_row = target_surface_data + (nStride*y);
2076 unsigned char *xor_row = xor_surface_data + (nStride*y);
2077 unsigned char *true_data = true_row + (nUnscaledExtentsLeft * 4);
2078 unsigned char *xor_data = xor_row + (nUnscaledExtentsLeft * 4);
2079 for (sal_Int32 x = nUnscaledExtentsLeft; x < nUnscaledExtentsRight; ++x)
2081 sal_uInt8 a = true_data[SVP_CAIRO_ALPHA];
2082 sal_uInt8 xor_a = xor_data[SVP_CAIRO_ALPHA];
2083 sal_uInt8 b = unpremultiply_table[a][true_data[SVP_CAIRO_BLUE]] ^
2084 unpremultiply_table[xor_a][xor_data[SVP_CAIRO_BLUE]];
2085 sal_uInt8 g = unpremultiply_table[a][true_data[SVP_CAIRO_GREEN]] ^
2086 unpremultiply_table[xor_a][xor_data[SVP_CAIRO_GREEN]];
2087 sal_uInt8 r = unpremultiply_table[a][true_data[SVP_CAIRO_RED]] ^
2088 unpremultiply_table[xor_a][xor_data[SVP_CAIRO_RED]];
2089 true_data[SVP_CAIRO_BLUE] = premultiply_table[a][b];
2090 true_data[SVP_CAIRO_GREEN] = premultiply_table[a][g];
2091 true_data[SVP_CAIRO_RED] = premultiply_table[a][r];
2092 true_data+=4;
2093 xor_data+=4;
2096 cairo_surface_mark_dirty(target_surface);
2098 if (target_surface != m_pSurface)
2100 cairo_t* copycr = cairo_create(m_pSurface);
2101 //unlikely case we couldn't use m_pSurface directly, copy contents
2102 //back from image surface
2103 cairo_rectangle(copycr, nExtentsLeft, nExtentsTop,
2104 nExtentsRight - nExtentsLeft,
2105 nExtentsBottom - nExtentsTop);
2106 cairo_set_source_surface(copycr, target_surface, 0, 0);
2107 cairo_paint(copycr);
2108 cairo_destroy(copycr);
2109 cairo_surface_destroy(target_surface);
2112 cairo_surface_destroy(surface);
2115 cairo_destroy(cr); // unref
2117 DamageHandler* pDamage = static_cast<DamageHandler*>(cairo_surface_get_user_data(m_pSurface, getDamageKey()));
2119 if (pDamage)
2121 pDamage->damaged(pDamage->handle, nExtentsLeft, nExtentsTop,
2122 nExtentsRight - nExtentsLeft,
2123 nExtentsBottom - nExtentsTop);
2127 #if ENABLE_CAIRO_CANVAS
2128 bool SvpSalGraphics::SupportsCairo() const
2130 return false;
2133 cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
2135 return cairo::SurfaceSharedPtr();
2138 cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/) const
2140 return cairo::SurfaceSharedPtr();
2143 cairo::SurfaceSharedPtr SvpSalGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/, const BitmapSystemData& /*rData*/, const Size& /*rSize*/) const
2145 return cairo::SurfaceSharedPtr();
2148 css::uno::Any SvpSalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/, const basegfx::B2ISize& /*rSize*/) const
2150 return css::uno::Any();
2153 #endif // ENABLE_CAIRO_CANVAS
2155 SystemGraphicsData SvpSalGraphics::GetGraphicsData() const
2157 return SystemGraphicsData();
2160 bool SvpSalGraphics::supportsOperation(OutDevSupportType eType) const
2162 switch (eType)
2164 case OutDevSupportType::TransparentRect:
2165 case OutDevSupportType::B2DDraw:
2166 return true;
2168 return false;
2171 GlyphCache& SvpSalGraphics::getPlatformGlyphCache()
2173 GenericUnixSalData* const pSalData(GetGenericUnixSalData());
2174 assert(pSalData);
2175 return *pSalData->GetGlyphCache();
2178 void dl_cairo_surface_set_device_scale(cairo_surface_t *surface, double x_scale, double y_scale)
2180 static auto func = reinterpret_cast<void(*)(cairo_surface_t*, double, double)>(
2181 dlsym(nullptr, "cairo_surface_set_device_scale"));
2182 if (func)
2183 func(surface, x_scale, y_scale);
2186 void dl_cairo_surface_get_device_scale(cairo_surface_t *surface, double* x_scale, double* y_scale)
2188 static auto func = reinterpret_cast<void(*)(cairo_surface_t*, double*, double*)>(
2189 dlsym(nullptr, "cairo_surface_get_device_scale"));
2190 if (func)
2191 func(surface, x_scale, y_scale);
2192 else
2194 if (x_scale)
2195 *x_scale = 1.0;
2196 if (y_scale)
2197 *y_scale = 1.0;
2201 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */