Version 6.1.4.1, tag libreoffice-6.1.4.1
[LibreOffice.git] / vcl / headless / svpgdi.cxx
blobf4ef3b21fe339ee82b63895aed686d8a2fc418ce
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 #include <headless/svpgdi.hxx>
22 #include <headless/svpbmp.hxx>
23 #include <headless/svpframe.hxx>
24 #include <headless/svpcairotextrender.hxx>
25 #include <saldatabasic.hxx>
27 #include <o3tl/safeint.hxx>
28 #include <vcl/sysdata.hxx>
29 #include <config_cairo_canvas.h>
30 #include <basegfx/numeric/ftools.hxx>
31 #include <basegfx/range/b2drange.hxx>
32 #include <basegfx/range/b2ibox.hxx>
33 #include <basegfx/polygon/b2dpolypolygon.hxx>
34 #include <basegfx/polygon/b2dpolypolygontools.hxx>
35 #include <basegfx/polygon/b2dpolygon.hxx>
36 #include <basegfx/polygon/b2dpolygontools.hxx>
38 #include <cairo.h>
40 #if ENABLE_CAIRO_CANVAS
41 # if defined CAIRO_VERSION && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0)
42 # define CAIRO_OPERATOR_DIFFERENCE (static_cast<cairo_operator_t>(23))
43 # endif
44 #endif
46 namespace
48 basegfx::B2DRange getClipBox(cairo_t* cr)
50 double x1, y1, x2, y2;
52 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
54 return basegfx::B2DRange(x1, y1, x2, y2);
57 basegfx::B2DRange getFillDamage(cairo_t* cr)
59 double x1, y1, x2, y2;
61 cairo_fill_extents(cr, &x1, &y1, &x2, &y2);
63 return basegfx::B2DRange(x1, y1, x2, y2);
66 basegfx::B2DRange getClippedFillDamage(cairo_t* cr)
68 basegfx::B2DRange aDamageRect(getFillDamage(cr));
69 aDamageRect.intersect(getClipBox(cr));
70 return aDamageRect;
73 basegfx::B2DRange getStrokeDamage(cairo_t* cr)
75 double x1, y1, x2, y2;
77 cairo_stroke_extents(cr, &x1, &y1, &x2, &y2);
79 return basegfx::B2DRange(x1, y1, x2, y2);
82 basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr)
84 basegfx::B2DRange aDamageRect(getStrokeDamage(cr));
85 aDamageRect.intersect(getClipBox(cr));
86 return aDamageRect;
90 bool SvpSalGraphics::blendBitmap( const SalTwoRect&, const SalBitmap& /*rBitmap*/ )
92 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::blendBitmap case");
93 return false;
96 bool SvpSalGraphics::blendAlphaBitmap( const SalTwoRect&, const SalBitmap&, const SalBitmap&, const SalBitmap& )
98 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::blendAlphaBitmap case");
99 return false;
102 namespace
104 cairo_format_t getCairoFormat(const BitmapBuffer& rBuffer)
106 cairo_format_t nFormat;
107 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
108 assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 24 || rBuffer.mnBitCount == 1);
109 #else
110 assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 1);
111 #endif
113 if (rBuffer.mnBitCount == 32)
114 nFormat = CAIRO_FORMAT_ARGB32;
115 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
116 else if (rBuffer.mnBitCount == 24)
117 nFormat = CAIRO_FORMAT_RGB24_888;
118 #endif
119 else
120 nFormat = CAIRO_FORMAT_A1;
121 return nFormat;
124 void Toggle1BitTransparency(const BitmapBuffer& rBuf)
126 assert(rBuf.maPalette.GetBestIndex(BitmapColor(COL_BLACK)) == 0);
127 // TODO: make upper layers use standard alpha
128 if (getCairoFormat(rBuf) == CAIRO_FORMAT_A1)
130 const int nImageSize = rBuf.mnHeight * rBuf.mnScanlineSize;
131 unsigned char* pDst = rBuf.mpBits;
132 for (int i = nImageSize; --i >= 0; ++pDst)
133 *pDst = ~*pDst;
137 std::unique_ptr<BitmapBuffer> FastConvert24BitRgbTo32BitCairo(const BitmapBuffer* pSrc)
139 if (pSrc == nullptr)
140 return nullptr;
142 assert(pSrc->mnFormat == SVP_24BIT_FORMAT);
143 const long nWidth = pSrc->mnWidth;
144 const long nHeight = pSrc->mnHeight;
145 std::unique_ptr<BitmapBuffer> pDst(new BitmapBuffer);
146 pDst->mnFormat = (ScanlineFormat::N32BitTcArgb | ScanlineFormat::TopDown);
147 pDst->mnWidth = nWidth;
148 pDst->mnHeight = nHeight;
149 pDst->mnBitCount = 32;
150 pDst->maColorMask = pSrc->maColorMask;
151 pDst->maPalette = pSrc->maPalette;
153 long nScanlineBase;
154 const bool bFail = o3tl::checked_multiply<long>(pDst->mnBitCount, nWidth, nScanlineBase);
155 if (bFail)
157 SAL_WARN("vcl.gdi", "checked multiply failed");
158 pDst->mpBits = nullptr;
159 return nullptr;
162 pDst->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
163 if (pDst->mnScanlineSize < nScanlineBase/8)
165 SAL_WARN("vcl.gdi", "scanline calculation wraparound");
166 pDst->mpBits = nullptr;
167 return nullptr;
172 pDst->mpBits = new sal_uInt8[ pDst->mnScanlineSize * nHeight ];
174 catch (const std::bad_alloc&)
176 // memory exception, clean up
177 pDst->mpBits = nullptr;
178 return nullptr;
181 for (long y = 0; y < nHeight; ++y)
183 sal_uInt8* pS = pSrc->mpBits + y * pSrc->mnScanlineSize;
184 sal_uInt8* pD = pDst->mpBits + y * pDst->mnScanlineSize;
185 for (long x = 0; x < nWidth; ++x)
187 #if defined ANDROID
188 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N32BitTcRgba, "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
189 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N24BitTcRgb, "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
190 pD[0] = pS[0];
191 pD[1] = pS[1];
192 pD[2] = pS[2];
193 pD[3] = 0xff; // Alpha
194 #elif defined OSL_BIGENDIAN
195 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N32BitTcArgb, "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
196 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N24BitTcRgb, "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
197 pD[0] = 0xff; // Alpha
198 pD[1] = pS[0];
199 pD[2] = pS[1];
200 pD[3] = pS[2];
201 #else
202 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N32BitTcBgra, "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
203 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N24BitTcBgr, "Expected SVP_24BIT_FORMAT set to N24BitTcBgr");
204 pD[0] = pS[0];
205 pD[1] = pS[1];
206 pD[2] = pS[2];
207 pD[3] = 0xff; // Alpha
208 #endif
210 pS += 3;
211 pD += 4;
215 return pDst;
218 class SourceHelper
220 public:
221 explicit SourceHelper(const SalBitmap& rSourceBitmap, const bool bForceARGB32 = false)
222 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
223 : m_bForceARGB32(bForceARGB32)
224 #endif
226 const SvpSalBitmap& rSrcBmp = static_cast<const SvpSalBitmap&>(rSourceBitmap);
227 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
228 if ((rSrcBmp.GetBitCount() != 32 && rSrcBmp.GetBitCount() != 24) || bForceARGB32)
229 #else
230 (void)bForceARGB32;
231 if (rSrcBmp.GetBitCount() != 32)
232 #endif
234 //big stupid copy here
235 static bool bWarnedOnce = false;
236 SAL_WARN_IF(!bWarnedOnce, "vcl.gdi", "non default depth bitmap, slow convert, upscale the input");
237 bWarnedOnce = true;
239 const BitmapBuffer* pSrc = rSrcBmp.GetBuffer();
240 const SalTwoRect aTwoRect = { 0, 0, pSrc->mnWidth, pSrc->mnHeight,
241 0, 0, pSrc->mnWidth, pSrc->mnHeight };
242 std::unique_ptr<BitmapBuffer> pTmp = (pSrc->mnFormat == SVP_24BIT_FORMAT
243 ? FastConvert24BitRgbTo32BitCairo(pSrc)
244 : StretchAndConvert(*pSrc, aTwoRect, SVP_CAIRO_FORMAT));
245 aTmpBmp.Create(std::move(pTmp));
247 assert(aTmpBmp.GetBitCount() == 32);
248 source = SvpSalGraphics::createCairoSurface(aTmpBmp.GetBuffer());
250 else
251 source = SvpSalGraphics::createCairoSurface(rSrcBmp.GetBuffer());
253 ~SourceHelper()
255 cairo_surface_destroy(source);
257 cairo_surface_t* getSurface()
259 return source;
261 void mark_dirty()
263 cairo_surface_mark_dirty(source);
265 unsigned char* getBits(sal_Int32 &rStride)
267 cairo_surface_flush(source);
269 unsigned char *mask_data = cairo_image_surface_get_data(source);
271 const cairo_format_t nFormat = cairo_image_surface_get_format(source);
272 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
273 if (!m_bForceARGB32)
274 assert(nFormat == CAIRO_FORMAT_RGB24_888 && "Expected RGB24_888 image");
275 else
276 #endif
277 assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here");
279 rStride = cairo_format_stride_for_width(nFormat, cairo_image_surface_get_width(source));
281 return mask_data;
283 private:
284 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
285 const bool m_bForceARGB32;
286 #endif
287 SvpSalBitmap aTmpBmp;
288 cairo_surface_t* source;
290 SourceHelper(const SourceHelper&) = delete;
291 SourceHelper& operator=(const SourceHelper&) = delete;
294 class MaskHelper
296 public:
297 explicit MaskHelper(const SalBitmap& rAlphaBitmap)
299 const SvpSalBitmap& rMask = static_cast<const SvpSalBitmap&>(rAlphaBitmap);
300 const BitmapBuffer* pMaskBuf = rMask.GetBuffer();
302 if (rAlphaBitmap.GetBitCount() == 8)
304 // the alpha values need to be inverted for Cairo
305 // so big stupid copy and invert here
306 const int nImageSize = pMaskBuf->mnHeight * pMaskBuf->mnScanlineSize;
307 pAlphaBits.reset( new unsigned char[nImageSize] );
308 memcpy(pAlphaBits.get(), pMaskBuf->mpBits, nImageSize);
310 // TODO: make upper layers use standard alpha
311 sal_uInt32* pLDst = reinterpret_cast<sal_uInt32*>(pAlphaBits.get());
312 for( int i = nImageSize/sizeof(sal_uInt32); --i >= 0; ++pLDst )
313 *pLDst = ~*pLDst;
314 assert(reinterpret_cast<unsigned char*>(pLDst) == pAlphaBits.get()+nImageSize);
316 mask = cairo_image_surface_create_for_data(pAlphaBits.get(),
317 CAIRO_FORMAT_A8,
318 pMaskBuf->mnWidth, pMaskBuf->mnHeight,
319 pMaskBuf->mnScanlineSize);
321 else
323 // the alpha values need to be inverted for Cairo
324 // so big stupid copy and invert here
325 const int nImageSize = pMaskBuf->mnHeight * pMaskBuf->mnScanlineSize;
326 pAlphaBits.reset( new unsigned char[nImageSize] );
327 memcpy(pAlphaBits.get(), pMaskBuf->mpBits, nImageSize);
329 const sal_Int32 nBlackIndex = pMaskBuf->maPalette.GetBestIndex(BitmapColor(COL_BLACK));
330 if (nBlackIndex == 0)
332 // TODO: make upper layers use standard alpha
333 unsigned char* pDst = pAlphaBits.get();
334 for (int i = nImageSize; --i >= 0; ++pDst)
335 *pDst = ~*pDst;
338 mask = cairo_image_surface_create_for_data(pAlphaBits.get(),
339 CAIRO_FORMAT_A1,
340 pMaskBuf->mnWidth, pMaskBuf->mnHeight,
341 pMaskBuf->mnScanlineSize);
344 ~MaskHelper()
346 cairo_surface_destroy(mask);
348 cairo_surface_t* getMask()
350 return mask;
352 private:
353 cairo_surface_t *mask;
354 std::unique_ptr<unsigned char[]> pAlphaBits;
356 MaskHelper(const MaskHelper&) = delete;
357 MaskHelper& operator=(const MaskHelper&) = delete;
361 bool SvpSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap )
363 if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1)
365 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap alpha depth case: " << rAlphaBitmap.GetBitCount());
366 return false;
369 SourceHelper aSurface(rSourceBitmap);
370 cairo_surface_t* source = aSurface.getSurface();
371 if (!source)
373 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
374 return false;
377 MaskHelper aMask(rAlphaBitmap);
378 cairo_surface_t *mask = aMask.getMask();
379 if (!mask)
381 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
382 return false;
385 cairo_t* cr = getCairoContext(false);
386 clipRegion(cr);
388 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
390 basegfx::B2DRange extents = getClippedFillDamage(cr);
392 cairo_clip(cr);
394 cairo_pattern_t* maskpattern = cairo_pattern_create_for_surface(mask);
395 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
396 double fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth;
397 double fYScale = static_cast<double>(rTR.mnDestHeight)/rTR.mnSrcHeight;
398 cairo_scale(cr, fXScale, fYScale);
399 cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
401 //tdf#114117 when stretching a single pixel width/height source to fit an area
402 //set extend and filter to stretch it with simplest expected interpolation
403 if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
405 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
406 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
407 cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
408 cairo_pattern_set_extend(maskpattern, CAIRO_EXTEND_REPEAT);
409 cairo_pattern_set_filter(maskpattern, CAIRO_FILTER_NEAREST);
412 //this block is just "cairo_mask_surface", but we have to make it explicit
413 //because of the cairo_pattern_set_filter etc we may want applied
414 cairo_matrix_t matrix;
415 cairo_matrix_init_translate(&matrix, rTR.mnSrcX, rTR.mnSrcY);
416 cairo_pattern_set_matrix(maskpattern, &matrix);
417 cairo_mask(cr, maskpattern);
419 cairo_pattern_destroy(maskpattern);
421 releaseCairoContext(cr, false, extents);
423 return true;
426 bool SvpSalGraphics::drawTransformedBitmap(
427 const basegfx::B2DPoint& rNull,
428 const basegfx::B2DPoint& rX,
429 const basegfx::B2DPoint& rY,
430 const SalBitmap& rSourceBitmap,
431 const SalBitmap* pAlphaBitmap)
433 if (pAlphaBitmap && pAlphaBitmap->GetBitCount() != 8 && pAlphaBitmap->GetBitCount() != 1)
435 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap alpha depth case: " << pAlphaBitmap->GetBitCount());
436 return false;
439 SourceHelper aSurface(rSourceBitmap);
440 cairo_surface_t* source = aSurface.getSurface();
441 if (!source)
443 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
444 return false;
447 std::unique_ptr<MaskHelper> xMask;
448 cairo_surface_t *mask = nullptr;
449 if (pAlphaBitmap)
451 xMask.reset(new MaskHelper(*pAlphaBitmap));
452 mask = xMask->getMask();
453 if (!mask)
455 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
456 return false;
460 const Size aSize = rSourceBitmap.GetSize();
462 cairo_t* cr = getCairoContext(false);
463 clipRegion(cr);
465 // setup the image transformation
466 // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
467 const basegfx::B2DVector aXRel = rX - rNull;
468 const basegfx::B2DVector aYRel = rY - rNull;
469 cairo_matrix_t matrix;
470 cairo_matrix_init(&matrix,
471 aXRel.getX()/aSize.Width(), aXRel.getY()/aSize.Width(),
472 aYRel.getX()/aSize.Height(), aYRel.getY()/aSize.Height(),
473 rNull.getX(), rNull.getY());
475 cairo_transform(cr, &matrix);
477 cairo_rectangle(cr, 0, 0, aSize.Width(), aSize.Height());
478 basegfx::B2DRange extents = getClippedFillDamage(cr);
479 cairo_clip(cr);
481 cairo_set_source_surface(cr, source, 0, 0);
482 if (mask)
483 cairo_mask_surface(cr, mask, 0, 0);
484 else
485 cairo_paint(cr);
487 releaseCairoContext(cr, false, extents);
489 return true;
492 void SvpSalGraphics::clipRegion(cairo_t* cr)
494 RectangleVector aRectangles;
495 if (!m_aClipRegion.IsEmpty())
497 m_aClipRegion.GetRegionRectangles(aRectangles);
499 if (!aRectangles.empty())
501 for (auto const& rectangle : aRectangles)
503 cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(), rectangle.GetHeight());
505 cairo_clip(cr);
509 bool SvpSalGraphics::drawAlphaRect(long nX, long nY, long nWidth, long nHeight, sal_uInt8 nTransparency)
511 cairo_t* cr = getCairoContext(false);
512 clipRegion(cr);
514 const double fTransparency = (100 - nTransparency) * (1.0/100);
516 basegfx::B2DRange extents(0, 0, 0, 0);
518 cairo_rectangle(cr, nX, nY, nWidth, nHeight);
520 if (m_aFillColor != SALCOLOR_NONE)
522 cairo_set_source_rgba(cr, m_aFillColor.GetRed()/255.0,
523 m_aFillColor.GetGreen()/255.0,
524 m_aFillColor.GetBlue()/255.0,
525 fTransparency);
527 if (m_aLineColor == SALCOLOR_NONE)
528 extents = getClippedFillDamage(cr);
530 cairo_fill_preserve(cr);
533 if (m_aLineColor != SALCOLOR_NONE)
535 cairo_set_source_rgba(cr, m_aLineColor.GetRed()/255.0,
536 m_aLineColor.GetGreen()/255.0,
537 m_aLineColor.GetBlue()/255.0,
538 fTransparency);
540 extents = getClippedStrokeDamage(cr);
542 cairo_stroke_preserve(cr);
545 releaseCairoContext(cr, false, extents);
547 return true;
550 SvpSalGraphics::SvpSalGraphics()
551 : m_pSurface(nullptr)
552 , m_fScale(1.0)
553 , m_aLineColor(Color(0x00, 0x00, 0x00))
554 , m_aFillColor(Color(0xFF, 0xFF, 0XFF))
555 , m_ePaintMode(PaintMode::Over)
556 , m_aTextRenderImpl(*this)
560 SvpSalGraphics::~SvpSalGraphics()
564 void SvpSalGraphics::setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize)
566 m_pSurface = pSurface;
567 m_aFrameSize = rSize;
568 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
569 cairo_surface_get_device_scale(pSurface, &m_fScale, nullptr);
570 #endif
571 ResetClipRegion();
574 void SvpSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY )
576 rDPIX = rDPIY = 96;
579 sal_uInt16 SvpSalGraphics::GetBitCount() const
581 if (cairo_surface_get_content(m_pSurface) != CAIRO_CONTENT_COLOR_ALPHA)
582 return 1;
583 return 32;
586 long SvpSalGraphics::GetGraphicsWidth() const
588 return m_pSurface ? m_aFrameSize.getX() : 0;
591 void SvpSalGraphics::ResetClipRegion()
593 m_aClipRegion.SetNull();
596 bool SvpSalGraphics::setClipRegion( const vcl::Region& i_rClip )
598 m_aClipRegion = i_rClip;
599 return true;
602 void SvpSalGraphics::SetLineColor()
604 m_aLineColor = SALCOLOR_NONE;
607 void SvpSalGraphics::SetLineColor( Color nColor )
609 m_aLineColor = nColor;
612 void SvpSalGraphics::SetFillColor()
614 m_aFillColor = SALCOLOR_NONE;
617 void SvpSalGraphics::SetFillColor( Color nColor )
619 m_aFillColor = nColor;
622 void SvpSalGraphics::SetXORMode(bool bSet )
624 m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over;
627 void SvpSalGraphics::SetROPLineColor( SalROPColor nROPColor )
629 switch( nROPColor )
631 case SalROPColor::N0:
632 m_aLineColor = Color(0, 0, 0);
633 break;
634 case SalROPColor::N1:
635 m_aLineColor = Color(0xff, 0xff, 0xff);
636 break;
637 case SalROPColor::Invert:
638 m_aLineColor = Color(0xff, 0xff, 0xff);
639 break;
643 void SvpSalGraphics::SetROPFillColor( SalROPColor nROPColor )
645 switch( nROPColor )
647 case SalROPColor::N0:
648 m_aFillColor = Color(0, 0, 0);
649 break;
650 case SalROPColor::N1:
651 m_aFillColor = Color(0xff, 0xff, 0xff);
652 break;
653 case SalROPColor::Invert:
654 m_aFillColor = Color(0xff, 0xff, 0xff);
655 break;
659 void SvpSalGraphics::drawPixel( long nX, long nY )
661 if (m_aLineColor != SALCOLOR_NONE)
663 drawPixel(nX, nY, m_aLineColor);
667 void SvpSalGraphics::drawPixel( long nX, long nY, Color nColor )
669 Color aOrigFillColor = m_aFillColor;
670 Color aOrigLineColor = m_aLineColor;
672 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+1, nY+1));
673 m_aLineColor = SALCOLOR_NONE;
674 m_aFillColor = nColor;
676 drawPolyPolygon(basegfx::B2DPolyPolygon(aRect));
678 m_aFillColor = aOrigFillColor;
679 m_aLineColor = aOrigLineColor;
682 void SvpSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight )
684 // because of the -1 hack we have to do fill and draw separately
685 Color aOrigFillColor = m_aFillColor;
686 Color aOrigLineColor = m_aLineColor;
687 m_aFillColor = SALCOLOR_NONE;
688 m_aLineColor = SALCOLOR_NONE;
690 if (aOrigFillColor != SALCOLOR_NONE)
692 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight));
693 m_aFillColor = aOrigFillColor;
694 drawPolyPolygon(basegfx::B2DPolyPolygon(aRect));
695 m_aFillColor = SALCOLOR_NONE;
698 if (aOrigLineColor != SALCOLOR_NONE)
700 // need same -1 hack as X11SalGraphicsImpl::drawRect
701 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle( nX, nY, nX+nWidth-1, nY+nHeight-1));
702 m_aLineColor = aOrigLineColor;
703 drawPolyPolygon(basegfx::B2DPolyPolygon(aRect));
704 m_aLineColor = SALCOLOR_NONE;
707 m_aFillColor = aOrigFillColor;
708 m_aLineColor = aOrigLineColor;
711 void SvpSalGraphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry)
713 basegfx::B2DPolygon aPoly;
714 aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
715 for (sal_uInt32 i = 1; i < nPoints; ++i)
716 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
717 aPoly.setClosed(false);
719 drawPolyLine(aPoly, 0.0, basegfx::B2DVector(1.0, 1.0), basegfx::B2DLineJoin::Miter,
720 css::drawing::LineCap_BUTT, 15.0 * F_PI180 /*default*/);
723 void SvpSalGraphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
725 basegfx::B2DPolygon aPoly;
726 aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
727 for (sal_uInt32 i = 1; i < nPoints; ++i)
728 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
730 drawPolyPolygon(basegfx::B2DPolyPolygon(aPoly));
733 void SvpSalGraphics::drawPolyPolygon(sal_uInt32 nPoly,
734 const sal_uInt32* pPointCounts,
735 PCONSTSALPOINT* pPtAry)
737 basegfx::B2DPolyPolygon aPolyPoly;
738 for(sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
740 sal_uInt32 nPoints = pPointCounts[nPolygon];
741 if (nPoints)
743 PCONSTSALPOINT pPoints = pPtAry[nPolygon];
744 basegfx::B2DPolygon aPoly;
745 aPoly.append( basegfx::B2DPoint(pPoints->mnX, pPoints->mnY), nPoints);
746 for (sal_uInt32 i = 1; i < nPoints; ++i)
747 aPoly.setB2DPoint(i, basegfx::B2DPoint( pPoints[i].mnX, pPoints[i].mnY));
749 aPolyPoly.append(aPoly);
753 drawPolyPolygon(aPolyPoly);
756 static const basegfx::B2DPoint aHalfPointOfs(0.5, 0.5);
758 static void AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, bool bClosePath,
759 bool bPixelSnap, bool bLineDraw)
761 // short circuit if there is nothing to do
762 const int nPointCount = rPolygon.count();
763 if( nPointCount <= 0 )
765 return;
768 const bool bHasCurves = rPolygon.areControlPointsUsed();
769 basegfx::B2DPoint aLast;
771 for( int nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++ )
773 int nClosedIdx = nPointIdx;
774 if( nPointIdx >= nPointCount )
776 // prepare to close last curve segment if needed
777 if( bClosePath && (nPointIdx == nPointCount) )
779 nClosedIdx = 0;
781 else
783 break;
787 basegfx::B2DPoint aPoint = rPolygon.getB2DPoint( nClosedIdx );
789 if( bPixelSnap)
791 // snap device coordinates to full pixels
792 aPoint.setX( basegfx::fround( aPoint.getX() ) );
793 aPoint.setY( basegfx::fround( aPoint.getY() ) );
796 if( bLineDraw )
798 aPoint += aHalfPointOfs;
801 if( !nPointIdx )
803 // first point => just move there
804 cairo_move_to(cr, aPoint.getX(), aPoint.getY());
805 aLast = aPoint;
806 continue;
809 bool bPendingCurve = false;
810 if( bHasCurves )
812 bPendingCurve = rPolygon.isNextControlPointUsed( nPrevIdx );
813 bPendingCurve |= rPolygon.isPrevControlPointUsed( nClosedIdx );
816 if( !bPendingCurve ) // line segment
818 cairo_line_to(cr, aPoint.getX(), aPoint.getY());
820 else // cubic bezier segment
822 basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint( nPrevIdx );
823 basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint( nClosedIdx );
824 if( bLineDraw )
826 aCP1 += aHalfPointOfs;
827 aCP2 += aHalfPointOfs;
830 // tdf#99165 if the control points are 'empty', create the mathematical
831 // correct replacement ones to avoid problems with the graphical sub-system
832 // tdf#101026 The 1st attempt to create a mathematically correct replacement control
833 // vector was wrong. Best alternative is one as close as possible which means short.
834 if (aCP1.equal(aLast))
836 aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
839 if(aCP2.equal(aPoint))
841 aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005);
844 cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(),
845 aPoint.getX(), aPoint.getY());
848 aLast = aPoint;
851 if( bClosePath )
853 cairo_close_path(cr);
857 void SvpSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
859 basegfx::B2DPolygon aPoly;
860 aPoly.append(basegfx::B2DPoint(nX1, nY1), 2);
861 aPoly.setB2DPoint(1, basegfx::B2DPoint(nX2, nY2));
862 aPoly.setClosed(false);
864 cairo_t* cr = getCairoContext(false);
865 clipRegion(cr);
867 AddPolygonToPath(cr, aPoly, aPoly.isClosed(), !getAntiAliasB2DDraw(), true);
869 applyColor(cr, m_aLineColor);
871 basegfx::B2DRange extents = getClippedStrokeDamage(cr);
873 cairo_stroke(cr);
875 releaseCairoContext(cr, false, extents);
878 bool SvpSalGraphics::drawPolyLine(
879 const basegfx::B2DPolygon& rPolyLine,
880 double fTransparency,
881 const basegfx::B2DVector& rLineWidths,
882 basegfx::B2DLineJoin eLineJoin,
883 css::drawing::LineCap eLineCap,
884 double fMiterMinimumAngle)
886 // short circuit if there is nothing to do
887 const int nPointCount = rPolyLine.count();
888 if (nPointCount <= 0)
890 return true;
893 const bool bNoJoin = (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(rLineWidths.getX(), 0.0));
895 cairo_t* cr = getCairoContext(false);
896 clipRegion(cr);
898 // setup line attributes
899 cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
900 switch (eLineJoin)
902 case basegfx::B2DLineJoin::Bevel:
903 eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
904 break;
905 case basegfx::B2DLineJoin::Round:
906 eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
907 break;
908 case basegfx::B2DLineJoin::NONE:
909 case basegfx::B2DLineJoin::Miter:
910 eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
911 break;
914 // convert miter minimum angle to miter limit
915 double fMiterLimit = 1.0 / sin( fMiterMinimumAngle / 2.0);
917 // setup cap attribute
918 cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
920 switch (eLineCap)
922 default: // css::drawing::LineCap_BUTT:
924 eCairoLineCap = CAIRO_LINE_CAP_BUTT;
925 break;
927 case css::drawing::LineCap_ROUND:
929 eCairoLineCap = CAIRO_LINE_CAP_ROUND;
930 break;
932 case css::drawing::LineCap_SQUARE:
934 eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
935 break;
939 cairo_set_source_rgba(cr, m_aLineColor.GetRed()/255.0,
940 m_aLineColor.GetGreen()/255.0,
941 m_aLineColor.GetBlue()/255.0,
942 1.0-fTransparency);
944 cairo_set_line_join(cr, eCairoLineJoin);
945 cairo_set_line_cap(cr, eCairoLineCap);
946 cairo_set_line_width(cr, rLineWidths.getX());
947 cairo_set_miter_limit(cr, fMiterLimit);
950 basegfx::B2DRange extents(0, 0, 0, 0);
952 if (!bNoJoin)
954 AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !getAntiAliasB2DDraw(), true);
955 extents = getClippedStrokeDamage(cr);
956 cairo_stroke(cr);
958 else
960 // emulate rendering::PathJoinType::NONE by painting single edges
961 const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1);
962 basegfx::B2DPolygon aEdge;
963 aEdge.append(rPolyLine.getB2DPoint(0));
964 aEdge.append(basegfx::B2DPoint(0.0, 0.0));
966 for (sal_uInt32 i = 0; i < nEdgeCount; ++i)
968 const sal_uInt32 nNextIndex((i + 1) % nPointCount);
969 aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex));
970 aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i % nPointCount));
971 aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex));
973 AddPolygonToPath(cr, aEdge, false, !getAntiAliasB2DDraw(), true);
975 extents.expand(getStrokeDamage(cr));
977 cairo_stroke(cr);
979 // prepare next step
980 aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
983 extents.intersect(getClipBox(cr));
986 releaseCairoContext(cr, false, extents);
988 return true;
991 bool SvpSalGraphics::drawPolyLineBezier( sal_uInt32,
992 const SalPoint*,
993 const PolyFlags* )
995 SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyLineBezier case");
996 return false;
999 bool SvpSalGraphics::drawPolygonBezier( sal_uInt32,
1000 const SalPoint*,
1001 const PolyFlags* )
1003 SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolygonBezier case");
1004 return false;
1007 bool SvpSalGraphics::drawPolyPolygonBezier( sal_uInt32,
1008 const sal_uInt32*,
1009 const SalPoint* const*,
1010 const PolyFlags* const* )
1012 SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyPolygonBezier case");
1013 return false;
1016 void SvpSalGraphics::setupPolyPolygon(cairo_t* cr, const basegfx::B2DPolyPolygon& rPolyPoly)
1018 clipRegion(cr);
1020 for (const auto & rPoly : rPolyPoly)
1021 AddPolygonToPath(cr, rPoly, true, !getAntiAliasB2DDraw(), m_aLineColor != SALCOLOR_NONE);
1024 bool SvpSalGraphics::drawPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPoly, double fTransparency)
1026 cairo_t* cr = getCairoContext(true);
1028 setupPolyPolygon(cr, rPolyPoly);
1030 basegfx::B2DRange extents(0, 0, 0, 0);
1032 if (m_aFillColor != SALCOLOR_NONE)
1034 cairo_set_source_rgba(cr, m_aFillColor.GetRed()/255.0,
1035 m_aFillColor.GetGreen()/255.0,
1036 m_aFillColor.GetBlue()/255.0,
1037 1.0-fTransparency);
1039 if (m_aLineColor == SALCOLOR_NONE)
1040 extents = getClippedFillDamage(cr);
1042 cairo_fill_preserve(cr);
1045 if (m_aLineColor != SALCOLOR_NONE)
1047 cairo_set_source_rgba(cr, m_aLineColor.GetRed()/255.0,
1048 m_aLineColor.GetGreen()/255.0,
1049 m_aLineColor.GetBlue()/255.0,
1050 1.0-fTransparency);
1052 extents = getClippedStrokeDamage(cr);
1054 cairo_stroke_preserve(cr);
1057 releaseCairoContext(cr, true, extents);
1059 return true;
1062 void SvpSalGraphics::applyColor(cairo_t *cr, Color aColor)
1064 if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
1066 cairo_set_source_rgba(cr, aColor.GetRed()/255.0,
1067 aColor.GetGreen()/255.0,
1068 aColor.GetBlue()/255.0,
1069 1.0);
1071 else
1073 double fSet = aColor == COL_BLACK ? 1.0 : 0.0;
1074 cairo_set_source_rgba(cr, 1, 1, 1, fSet);
1075 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1079 void SvpSalGraphics::drawPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPoly)
1081 cairo_t* cr = getCairoContext(true);
1083 setupPolyPolygon(cr, rPolyPoly);
1085 basegfx::B2DRange extents(0, 0, 0, 0);
1087 if (m_aFillColor != SALCOLOR_NONE)
1089 applyColor(cr, m_aFillColor);
1090 if (m_aLineColor == SALCOLOR_NONE)
1091 extents = getClippedFillDamage(cr);
1092 cairo_fill_preserve(cr);
1095 if (m_aLineColor != SALCOLOR_NONE)
1097 applyColor(cr, m_aLineColor);
1098 extents = getClippedStrokeDamage(cr);
1099 cairo_stroke_preserve(cr);
1102 releaseCairoContext(cr, true, extents);
1105 void SvpSalGraphics::copyArea( long nDestX,
1106 long nDestY,
1107 long nSrcX,
1108 long nSrcY,
1109 long nSrcWidth,
1110 long nSrcHeight,
1111 bool /*bWindowInvalidate*/ )
1113 SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
1114 copyBits(aTR, this);
1117 static basegfx::B2DRange renderSource(cairo_t* cr, const SalTwoRect& rTR,
1118 cairo_surface_t* source)
1120 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1122 basegfx::B2DRange extents = getClippedFillDamage(cr);
1124 cairo_clip(cr);
1126 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1127 double fXScale = 1.0f;
1128 double fYScale = 1.0f;
1129 if (rTR.mnSrcWidth != 0 && rTR.mnSrcHeight != 0) {
1130 fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth;
1131 fYScale = static_cast<double>(rTR.mnDestHeight)/rTR.mnSrcHeight;
1132 cairo_scale(cr, fXScale, fYScale);
1135 cairo_save(cr);
1136 cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
1137 if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
1139 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1140 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
1141 cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
1143 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1144 cairo_paint(cr);
1145 cairo_restore(cr);
1147 return extents;
1150 void SvpSalGraphics::copySource( const SalTwoRect& rTR,
1151 cairo_surface_t* source )
1153 cairo_t* cr = getCairoContext(false);
1154 clipRegion(cr);
1156 basegfx::B2DRange extents = renderSource(cr, rTR, source);
1158 releaseCairoContext(cr, false, extents);
1161 void SvpSalGraphics::copyBits( const SalTwoRect& rTR,
1162 SalGraphics* pSrcGraphics )
1164 SalTwoRect aTR(rTR);
1166 SvpSalGraphics* pSrc = pSrcGraphics ?
1167 static_cast<SvpSalGraphics*>(pSrcGraphics) : this;
1169 cairo_surface_t* source = pSrc->m_pSurface;
1171 cairo_surface_t *pCopy = nullptr;
1172 if (pSrc == this)
1174 //self copy is a problem, so dup source in that case
1175 pCopy = cairo_surface_create_similar(source,
1176 cairo_surface_get_content(m_pSurface),
1177 aTR.mnSrcWidth * m_fScale,
1178 aTR.mnSrcHeight * m_fScale);
1179 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
1180 cairo_surface_set_device_scale(pCopy, m_fScale, m_fScale);
1181 #endif
1182 cairo_t* cr = cairo_create(pCopy);
1183 cairo_set_source_surface(cr, source, -aTR.mnSrcX, -aTR.mnSrcY);
1184 cairo_rectangle(cr, 0, 0, aTR.mnSrcWidth, aTR.mnSrcHeight);
1185 cairo_fill(cr);
1186 cairo_destroy(cr);
1188 source = pCopy;
1190 aTR.mnSrcX = 0;
1191 aTR.mnSrcY = 0;
1194 copySource(aTR, source);
1196 if (pCopy)
1197 cairo_surface_destroy(pCopy);
1200 void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap)
1202 SourceHelper aSurface(rSourceBitmap);
1203 cairo_surface_t* source = aSurface.getSurface();
1204 copySource(rTR, source);
1207 void SvpSalGraphics::drawBitmap( const SalTwoRect& rTR,
1208 const SalBitmap& rSourceBitmap,
1209 const SalBitmap& rTransparentBitmap )
1211 drawAlphaBitmap(rTR, rSourceBitmap, rTransparentBitmap);
1214 static sal_uInt8 unpremultiply(sal_uInt8 c, sal_uInt8 a)
1216 return (a > 0) ? (c * 255 + a / 2) / a : 0;
1219 static sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a)
1221 return (c * a + 127) / 255;
1224 void SvpSalGraphics::drawMask( const SalTwoRect& rTR,
1225 const SalBitmap& rSalBitmap,
1226 Color nMaskColor )
1228 /** creates an image from the given rectangle, replacing all black pixels
1229 * with nMaskColor and make all other full transparent */
1230 SourceHelper aSurface(rSalBitmap, true); // The mask is argb32
1231 if (!aSurface.getSurface())
1233 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawMask case");
1234 return;
1236 sal_Int32 nStride;
1237 unsigned char *mask_data = aSurface.getBits(nStride);
1238 for (sal_Int32 y = rTR.mnSrcY ; y < rTR.mnSrcY + rTR.mnSrcHeight; ++y)
1240 unsigned char *row = mask_data + (nStride*y);
1241 unsigned char *data = row + (rTR.mnSrcX * 4);
1242 for (sal_Int32 x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x)
1244 sal_uInt8 b = unpremultiply(data[SVP_CAIRO_BLUE], data[SVP_CAIRO_ALPHA]);
1245 sal_uInt8 g = unpremultiply(data[SVP_CAIRO_GREEN], data[SVP_CAIRO_ALPHA]);
1246 sal_uInt8 r = unpremultiply(data[SVP_CAIRO_RED], data[SVP_CAIRO_ALPHA]);
1247 if (r == 0 && g == 0 && b == 0)
1249 data[0] = nMaskColor.GetBlue();
1250 data[1] = nMaskColor.GetGreen();
1251 data[2] = nMaskColor.GetRed();
1252 data[3] = 0xff;
1254 else
1256 data[0] = 0;
1257 data[1] = 0;
1258 data[2] = 0;
1259 data[3] = 0;
1261 data+=4;
1264 aSurface.mark_dirty();
1266 cairo_t* cr = getCairoContext(false);
1267 clipRegion(cr);
1269 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1271 basegfx::B2DRange extents = getClippedFillDamage(cr);
1273 cairo_clip(cr);
1275 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1276 double fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth;
1277 double fYScale = static_cast<double>(rTR.mnDestHeight)/rTR.mnSrcHeight;
1278 cairo_scale(cr, fXScale, fYScale);
1279 cairo_set_source_surface(cr, aSurface.getSurface(), -rTR.mnSrcX, -rTR.mnSrcY);
1280 if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
1282 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1283 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
1284 cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
1286 cairo_paint(cr);
1288 releaseCairoContext(cr, false, extents);
1291 SalBitmap* SvpSalGraphics::getBitmap( long nX, long nY, long nWidth, long nHeight )
1293 SvpSalBitmap* pBitmap = new SvpSalBitmap();
1294 BitmapPalette aPal;
1295 if (GetBitCount() == 1)
1297 aPal.SetEntryCount(2);
1298 aPal[0] = COL_BLACK;
1299 aPal[1] = COL_WHITE;
1302 if (!pBitmap->Create(Size(nWidth, nHeight), GetBitCount(), aPal))
1304 SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create bitmap");
1305 delete pBitmap;
1306 return nullptr;
1309 cairo_surface_t* target = SvpSalGraphics::createCairoSurface(pBitmap->GetBuffer());
1310 cairo_t* cr = cairo_create(target);
1312 SalTwoRect aTR(nX, nY, nWidth, nHeight, 0, 0, nWidth, nHeight);
1313 renderSource(cr, aTR, m_pSurface);
1315 cairo_destroy(cr);
1316 cairo_surface_destroy(target);
1318 Toggle1BitTransparency(*pBitmap->GetBuffer());
1320 return pBitmap;
1323 Color SvpSalGraphics::getPixel( long nX, long nY )
1325 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
1326 cairo_surface_t *target = cairo_surface_create_similar_image(m_pSurface,
1327 #else
1328 cairo_surface_t *target = cairo_image_surface_create(
1329 #endif
1330 CAIRO_FORMAT_ARGB32, 1, 1);
1332 cairo_t* cr = cairo_create(target);
1334 cairo_rectangle(cr, 0, 0, 1, 1);
1335 cairo_set_source_surface(cr, m_pSurface, -nX, -nY);
1336 cairo_paint(cr);
1337 cairo_destroy(cr);
1339 cairo_surface_flush(target);
1340 unsigned char *data = cairo_image_surface_get_data(target);
1341 sal_uInt8 b = unpremultiply(data[SVP_CAIRO_BLUE], data[SVP_CAIRO_ALPHA]);
1342 sal_uInt8 g = unpremultiply(data[SVP_CAIRO_GREEN], data[SVP_CAIRO_ALPHA]);
1343 sal_uInt8 r = unpremultiply(data[SVP_CAIRO_RED], data[SVP_CAIRO_ALPHA]);
1344 Color nRet = Color(r, g, b);
1346 cairo_surface_destroy(target);
1348 return nRet;
1351 namespace
1353 cairo_pattern_t * create_stipple()
1355 static unsigned char data[16] = { 0xFF, 0xFF, 0x00, 0x00,
1356 0xFF, 0xFF, 0x00, 0x00,
1357 0x00, 0x00, 0xFF, 0xFF,
1358 0x00, 0x00, 0xFF, 0xFF };
1359 cairo_surface_t* surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_A8, 4, 4, 4);
1360 cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface);
1361 cairo_surface_destroy(surface);
1362 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
1363 cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
1364 return pattern;
1368 void SvpSalGraphics::invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags)
1370 cairo_t* cr = getCairoContext(false);
1371 clipRegion(cr);
1373 basegfx::B2DRange extents(0, 0, 0, 0);
1375 AddPolygonToPath(cr, rPoly, true, !getAntiAliasB2DDraw(), false);
1377 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
1379 if (cairo_version() >= CAIRO_VERSION_ENCODE(1, 10, 0))
1381 cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
1383 else
1385 SAL_WARN("vcl.gdi", "SvpSalGraphics::invert, archaic cairo");
1388 if (nFlags & SalInvert::TrackFrame)
1390 cairo_set_line_width(cr, 2.0);
1391 const double dashLengths[2] = { 4.0, 4.0 };
1392 cairo_set_dash(cr, dashLengths, 2, 0);
1394 extents = getClippedStrokeDamage(cr);
1395 //see tdf#106577 under wayland, some pixel droppings seen, maybe we're
1396 //out by one somewhere, or cairo_stroke_extents is confused by
1397 //dashes/line width
1398 extents.grow(1);
1400 cairo_stroke(cr);
1402 else
1404 extents = getClippedFillDamage(cr);
1406 cairo_clip(cr);
1408 if (nFlags & SalInvert::N50)
1410 cairo_pattern_t *pattern = create_stipple();
1411 cairo_surface_t* surface = cairo_surface_create_similar(m_pSurface,
1412 cairo_surface_get_content(m_pSurface),
1413 extents.getWidth() * m_fScale,
1414 extents.getHeight() * m_fScale);
1416 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
1417 cairo_surface_set_device_scale(surface, m_fScale, m_fScale);
1418 #endif
1419 cairo_t* stipple_cr = cairo_create(surface);
1420 cairo_set_source_rgb(stipple_cr, 1.0, 1.0, 1.0);
1421 cairo_mask(stipple_cr, pattern);
1422 cairo_pattern_destroy(pattern);
1423 cairo_destroy(stipple_cr);
1424 cairo_mask_surface(cr, surface, extents.getMinX(), extents.getMinY());
1425 cairo_surface_destroy(surface);
1427 else
1429 cairo_paint(cr);
1433 releaseCairoContext(cr, false, extents);
1436 void SvpSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags )
1438 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight));
1440 invert(aRect, nFlags);
1443 void SvpSalGraphics::invert(sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags)
1445 basegfx::B2DPolygon aPoly;
1446 aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
1447 for (sal_uInt32 i = 1; i < nPoints; ++i)
1448 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
1449 aPoly.setClosed(true);
1451 invert(aPoly, nFlags);
1454 bool SvpSalGraphics::drawEPS( long, long, long, long, void*, sal_uLong )
1456 return false;
1459 namespace
1461 bool isCairoCompatible(const BitmapBuffer* pBuffer)
1463 if (!pBuffer)
1464 return false;
1466 // We use Cairo that supports 24-bit RGB.
1467 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
1468 if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 24 && pBuffer->mnBitCount != 1)
1469 #else
1470 if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 1)
1471 #endif
1472 return false;
1474 cairo_format_t nFormat = getCairoFormat(*pBuffer);
1475 return (cairo_format_stride_for_width(nFormat, pBuffer->mnWidth) == pBuffer->mnScanlineSize);
1479 cairo_surface_t* SvpSalGraphics::createCairoSurface(const BitmapBuffer *pBuffer)
1481 if (!isCairoCompatible(pBuffer))
1482 return nullptr;
1484 cairo_format_t nFormat = getCairoFormat(*pBuffer);
1485 cairo_surface_t *target =
1486 cairo_image_surface_create_for_data(pBuffer->mpBits,
1487 nFormat,
1488 pBuffer->mnWidth, pBuffer->mnHeight,
1489 pBuffer->mnScanlineSize);
1490 if (cairo_surface_status(target) != CAIRO_STATUS_SUCCESS)
1492 cairo_surface_destroy(target);
1493 return nullptr;
1495 return target;
1498 cairo_t* SvpSalGraphics::createTmpCompatibleCairoContext() const
1500 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
1501 cairo_surface_t *target = cairo_surface_create_similar_image(m_pSurface,
1502 #else
1503 cairo_surface_t *target = cairo_image_surface_create(
1504 #endif
1505 CAIRO_FORMAT_ARGB32,
1506 m_aFrameSize.getX() * m_fScale,
1507 m_aFrameSize.getY() * m_fScale);
1509 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
1510 cairo_surface_set_device_scale(target, m_fScale, m_fScale);
1511 #endif
1513 return cairo_create(target);
1516 cairo_t* SvpSalGraphics::getCairoContext(bool bXorModeAllowed) const
1518 cairo_t* cr;
1519 if (m_ePaintMode == PaintMode::Xor && bXorModeAllowed)
1520 cr = createTmpCompatibleCairoContext();
1521 else
1522 cr = cairo_create(m_pSurface);
1523 cairo_set_line_width(cr, 1);
1524 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1525 cairo_set_antialias(cr, getAntiAliasB2DDraw() ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
1526 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1527 return cr;
1530 cairo_user_data_key_t* SvpSalGraphics::getDamageKey()
1532 static cairo_user_data_key_t aDamageKey;
1533 return &aDamageKey;
1536 void SvpSalGraphics::releaseCairoContext(cairo_t* cr, bool bXorModeAllowed, const basegfx::B2DRange& rExtents) const
1538 const bool bXoring = (m_ePaintMode == PaintMode::Xor && bXorModeAllowed);
1540 if (rExtents.isEmpty())
1542 //nothing changed, return early
1543 if (bXoring)
1545 cairo_surface_t* surface = cairo_get_target(cr);
1546 cairo_surface_destroy(surface);
1548 cairo_destroy(cr);
1549 return;
1552 sal_Int32 nExtentsLeft(rExtents.getMinX()), nExtentsTop(rExtents.getMinY());
1553 sal_Int32 nExtentsRight(rExtents.getMaxX()), nExtentsBottom(rExtents.getMaxY());
1554 sal_Int32 nWidth = m_aFrameSize.getX();
1555 sal_Int32 nHeight = m_aFrameSize.getY();
1556 nExtentsLeft = std::max<sal_Int32>(nExtentsLeft, 0);
1557 nExtentsTop = std::max<sal_Int32>(nExtentsTop, 0);
1558 nExtentsRight = std::min<sal_Int32>(nExtentsRight, nWidth);
1559 nExtentsBottom = std::min<sal_Int32>(nExtentsBottom, nHeight);
1561 cairo_surface_t* surface = cairo_get_target(cr);
1562 cairo_surface_flush(surface);
1564 //For the most part we avoid the use of XOR these days, but there
1565 //are some edge cases where legacy stuff still supports it, so
1566 //emulate it (slowly) here.
1567 if (bXoring)
1569 cairo_surface_t* target_surface = m_pSurface;
1570 if (cairo_surface_get_type(target_surface) != CAIRO_SURFACE_TYPE_IMAGE)
1572 //in the unlikely case we can't use m_pSurface directly, copy contents
1573 //to another temp image surface
1574 cairo_t* copycr = createTmpCompatibleCairoContext();
1575 cairo_rectangle(copycr, nExtentsLeft, nExtentsTop,
1576 nExtentsRight - nExtentsLeft,
1577 nExtentsBottom - nExtentsTop);
1578 cairo_set_source_surface(copycr, m_pSurface, 0, 0);
1579 cairo_paint(copycr);
1580 target_surface = cairo_get_target(copycr);
1581 cairo_destroy(copycr);
1584 cairo_surface_flush(target_surface);
1585 unsigned char *target_surface_data = cairo_image_surface_get_data(target_surface);
1586 unsigned char *xor_surface_data = cairo_image_surface_get_data(surface);
1588 cairo_format_t nFormat = cairo_image_surface_get_format(target_surface);
1589 assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here");
1590 sal_Int32 nStride = cairo_format_stride_for_width(nFormat, nWidth * m_fScale);
1591 sal_Int32 nUnscaledExtentsLeft = nExtentsLeft * m_fScale;
1592 sal_Int32 nUnscaledExtentsRight = nExtentsRight * m_fScale;
1593 sal_Int32 nUnscaledExtentsTop = nExtentsTop * m_fScale;
1594 sal_Int32 nUnscaledExtentsBottom = nExtentsBottom * m_fScale;
1595 for (sal_Int32 y = nUnscaledExtentsTop; y < nUnscaledExtentsBottom; ++y)
1597 unsigned char *true_row = target_surface_data + (nStride*y);
1598 unsigned char *xor_row = xor_surface_data + (nStride*y);
1599 unsigned char *true_data = true_row + (nUnscaledExtentsLeft * 4);
1600 unsigned char *xor_data = xor_row + (nUnscaledExtentsLeft * 4);
1601 for (sal_Int32 x = nUnscaledExtentsLeft; x < nUnscaledExtentsRight; ++x)
1603 sal_uInt8 b = unpremultiply(true_data[SVP_CAIRO_BLUE], true_data[SVP_CAIRO_ALPHA]) ^
1604 unpremultiply(xor_data[SVP_CAIRO_BLUE], xor_data[SVP_CAIRO_ALPHA]);
1605 sal_uInt8 g = unpremultiply(true_data[SVP_CAIRO_GREEN], true_data[SVP_CAIRO_ALPHA]) ^
1606 unpremultiply(xor_data[SVP_CAIRO_GREEN], xor_data[SVP_CAIRO_ALPHA]);
1607 sal_uInt8 r = unpremultiply(true_data[SVP_CAIRO_RED], true_data[SVP_CAIRO_ALPHA]) ^
1608 unpremultiply(xor_data[SVP_CAIRO_RED], xor_data[SVP_CAIRO_ALPHA]);
1609 true_data[0] = premultiply(b, true_data[SVP_CAIRO_ALPHA]);
1610 true_data[1] = premultiply(g, true_data[SVP_CAIRO_ALPHA]);
1611 true_data[2] = premultiply(r, true_data[SVP_CAIRO_ALPHA]);
1612 true_data+=4;
1613 xor_data+=4;
1616 cairo_surface_mark_dirty(target_surface);
1618 if (target_surface != m_pSurface)
1620 cairo_t* copycr = cairo_create(m_pSurface);
1621 //unlikely case we couldn't use m_pSurface directly, copy contents
1622 //back from image surface
1623 cairo_rectangle(copycr, nExtentsLeft, nExtentsTop,
1624 nExtentsRight - nExtentsLeft,
1625 nExtentsBottom - nExtentsTop);
1626 cairo_set_source_surface(copycr, target_surface, 0, 0);
1627 cairo_paint(copycr);
1628 cairo_destroy(copycr);
1629 cairo_surface_destroy(target_surface);
1632 cairo_surface_destroy(surface);
1635 cairo_destroy(cr); // unref
1637 DamageHandler* pDamage = static_cast<DamageHandler*>(cairo_surface_get_user_data(m_pSurface, getDamageKey()));
1639 if (pDamage)
1641 pDamage->damaged(pDamage->handle, nExtentsLeft, nExtentsTop,
1642 nExtentsRight - nExtentsLeft,
1643 nExtentsBottom - nExtentsTop);
1647 #if ENABLE_CAIRO_CANVAS
1648 bool SvpSalGraphics::SupportsCairo() const
1650 return false;
1653 cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
1655 return cairo::SurfaceSharedPtr();
1658 cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/) const
1660 return cairo::SurfaceSharedPtr();
1663 cairo::SurfaceSharedPtr SvpSalGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/, const BitmapSystemData& /*rData*/, const Size& /*rSize*/) const
1665 return cairo::SurfaceSharedPtr();
1668 css::uno::Any SvpSalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/, const basegfx::B2ISize& /*rSize*/) const
1670 return css::uno::Any();
1673 #endif // ENABLE_CAIRO_CANVAS
1675 SystemGraphicsData SvpSalGraphics::GetGraphicsData() const
1677 return SystemGraphicsData();
1680 bool SvpSalGraphics::supportsOperation(OutDevSupportType eType) const
1682 switch (eType)
1684 case OutDevSupportType::TransparentRect:
1685 case OutDevSupportType::B2DDraw:
1686 return true;
1688 return false;
1691 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */