tdf#116301: write correct content type for diagramDrawing
[LibreOffice.git] / vcl / headless / svpgdi.cxx
blob3618a185ce1274b95837196a63f7b3fbf11dda9e
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 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 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 delete pDst;
160 return nullptr;
163 pDst->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
164 if (pDst->mnScanlineSize < nScanlineBase/8)
166 SAL_WARN("vcl.gdi", "scanline calculation wraparound");
167 pDst->mpBits = nullptr;
168 delete pDst;
169 return nullptr;
174 pDst->mpBits = new sal_uInt8[ pDst->mnScanlineSize * nHeight ];
176 catch (const std::bad_alloc&)
178 // memory exception, clean up
179 pDst->mpBits = nullptr;
180 delete pDst;
181 return nullptr;
184 for (long y = 0; y < nHeight; ++y)
186 sal_uInt8* pS = pSrc->mpBits + y * pSrc->mnScanlineSize;
187 sal_uInt8* pD = pDst->mpBits + y * pDst->mnScanlineSize;
188 for (long x = 0; x < nWidth; ++x)
190 #if defined ANDROID
191 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N32BitTcRgba, "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
192 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N24BitTcRgb, "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
193 pD[0] = pS[0];
194 pD[1] = pS[1];
195 pD[2] = pS[2];
196 pD[3] = 0xff; // Alpha
197 #elif defined OSL_BIGENDIAN
198 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N32BitTcArgb, "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
199 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N24BitTcRgb, "Expected SVP_24BIT_FORMAT set to N24BitTcRgb");
200 pD[0] = 0xff; // Alpha
201 pD[1] = pS[0];
202 pD[2] = pS[1];
203 pD[3] = pS[2];
204 #else
205 static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N32BitTcBgra, "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra");
206 static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) == ScanlineFormat::N24BitTcBgr, "Expected SVP_24BIT_FORMAT set to N24BitTcBgr");
207 pD[0] = pS[0];
208 pD[1] = pS[1];
209 pD[2] = pS[2];
210 pD[3] = 0xff; // Alpha
211 #endif
213 pS += 3;
214 pD += 4;
218 return pDst;
221 class SourceHelper
223 public:
224 explicit SourceHelper(const SalBitmap& rSourceBitmap, const bool bForceARGB32 = false)
225 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
226 : m_bForceARGB32(bForceARGB32)
227 #endif
229 const SvpSalBitmap& rSrcBmp = static_cast<const SvpSalBitmap&>(rSourceBitmap);
230 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
231 if ((rSrcBmp.GetBitCount() != 32 && rSrcBmp.GetBitCount() != 24) || bForceARGB32)
232 #else
233 (void)bForceARGB32;
234 if (rSrcBmp.GetBitCount() != 32)
235 #endif
237 //big stupid copy here
238 static bool bWarnedOnce = false;
239 SAL_WARN_IF(!bWarnedOnce, "vcl.gdi", "non default depth bitmap, slow convert, upscale the input");
240 bWarnedOnce = true;
242 const BitmapBuffer* pSrc = rSrcBmp.GetBuffer();
243 const SalTwoRect aTwoRect = { 0, 0, pSrc->mnWidth, pSrc->mnHeight,
244 0, 0, pSrc->mnWidth, pSrc->mnHeight };
245 BitmapBuffer* pTmp = (pSrc->mnFormat == SVP_24BIT_FORMAT
246 ? FastConvert24BitRgbTo32BitCairo(pSrc)
247 : StretchAndConvert(*pSrc, aTwoRect, SVP_CAIRO_FORMAT));
248 aTmpBmp.Create(pTmp);
250 assert(aTmpBmp.GetBitCount() == 32);
251 source = SvpSalGraphics::createCairoSurface(aTmpBmp.GetBuffer());
253 else
254 source = SvpSalGraphics::createCairoSurface(rSrcBmp.GetBuffer());
256 ~SourceHelper()
258 cairo_surface_destroy(source);
260 cairo_surface_t* getSurface()
262 return source;
264 void mark_dirty()
266 cairo_surface_mark_dirty(source);
268 unsigned char* getBits(sal_Int32 &rStride)
270 cairo_surface_flush(source);
272 unsigned char *mask_data = cairo_image_surface_get_data(source);
274 const cairo_format_t nFormat = cairo_image_surface_get_format(source);
275 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
276 if (!m_bForceARGB32)
277 assert(nFormat == CAIRO_FORMAT_RGB24_888 && "Expected RGB24_888 image");
278 else
279 #endif
280 assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here");
282 rStride = cairo_format_stride_for_width(nFormat, cairo_image_surface_get_width(source));
284 return mask_data;
286 private:
287 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
288 const bool m_bForceARGB32;
289 #endif
290 SvpSalBitmap aTmpBmp;
291 cairo_surface_t* source;
293 SourceHelper(const SourceHelper&) = delete;
294 SourceHelper& operator=(const SourceHelper&) = delete;
297 class MaskHelper
299 public:
300 explicit MaskHelper(const SalBitmap& rAlphaBitmap)
302 const SvpSalBitmap& rMask = static_cast<const SvpSalBitmap&>(rAlphaBitmap);
303 const BitmapBuffer* pMaskBuf = rMask.GetBuffer();
305 if (rAlphaBitmap.GetBitCount() == 8)
307 // the alpha values need to be inverted for Cairo
308 // so big stupid copy and invert here
309 const int nImageSize = pMaskBuf->mnHeight * pMaskBuf->mnScanlineSize;
310 pAlphaBits.reset( new unsigned char[nImageSize] );
311 memcpy(pAlphaBits.get(), pMaskBuf->mpBits, nImageSize);
313 // TODO: make upper layers use standard alpha
314 sal_uInt32* pLDst = reinterpret_cast<sal_uInt32*>(pAlphaBits.get());
315 for( int i = nImageSize/sizeof(sal_uInt32); --i >= 0; ++pLDst )
316 *pLDst = ~*pLDst;
317 assert(reinterpret_cast<unsigned char*>(pLDst) == pAlphaBits.get()+nImageSize);
319 mask = cairo_image_surface_create_for_data(pAlphaBits.get(),
320 CAIRO_FORMAT_A8,
321 pMaskBuf->mnWidth, pMaskBuf->mnHeight,
322 pMaskBuf->mnScanlineSize);
324 else
326 // the alpha values need to be inverted for Cairo
327 // so big stupid copy and invert here
328 const int nImageSize = pMaskBuf->mnHeight * pMaskBuf->mnScanlineSize;
329 pAlphaBits.reset( new unsigned char[nImageSize] );
330 memcpy(pAlphaBits.get(), pMaskBuf->mpBits, nImageSize);
332 const sal_Int32 nBlackIndex = pMaskBuf->maPalette.GetBestIndex(BitmapColor(COL_BLACK));
333 if (nBlackIndex == 0)
335 // TODO: make upper layers use standard alpha
336 unsigned char* pDst = pAlphaBits.get();
337 for (int i = nImageSize; --i >= 0; ++pDst)
338 *pDst = ~*pDst;
341 mask = cairo_image_surface_create_for_data(pAlphaBits.get(),
342 CAIRO_FORMAT_A1,
343 pMaskBuf->mnWidth, pMaskBuf->mnHeight,
344 pMaskBuf->mnScanlineSize);
347 ~MaskHelper()
349 cairo_surface_destroy(mask);
351 cairo_surface_t* getMask()
353 return mask;
355 private:
356 cairo_surface_t *mask;
357 std::unique_ptr<unsigned char[]> pAlphaBits;
359 MaskHelper(const MaskHelper&) = delete;
360 MaskHelper& operator=(const MaskHelper&) = delete;
364 bool SvpSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap )
366 if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1)
368 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap alpha depth case: " << rAlphaBitmap.GetBitCount());
369 return false;
372 SourceHelper aSurface(rSourceBitmap);
373 cairo_surface_t* source = aSurface.getSurface();
374 if (!source)
376 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
377 return false;
380 MaskHelper aMask(rAlphaBitmap);
381 cairo_surface_t *mask = aMask.getMask();
382 if (!mask)
384 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
385 return false;
388 cairo_t* cr = getCairoContext(false);
389 clipRegion(cr);
391 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
393 basegfx::B2DRange extents = getClippedFillDamage(cr);
395 cairo_clip(cr);
397 cairo_pattern_t* maskpattern = cairo_pattern_create_for_surface(mask);
398 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
399 double fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth;
400 double fYScale = static_cast<double>(rTR.mnDestHeight)/rTR.mnSrcHeight;
401 cairo_scale(cr, fXScale, fYScale);
402 cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
404 //tdf#114117 when stretching a single pixel width/height source to fit an area
405 //set extend and filter to stretch it with simplest expected interpolation
406 if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
408 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
409 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
410 cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
411 cairo_pattern_set_extend(maskpattern, CAIRO_EXTEND_REPEAT);
412 cairo_pattern_set_filter(maskpattern, CAIRO_FILTER_NEAREST);
415 //this block is just "cairo_mask_surface", but we have to make it explicit
416 //because of the cairo_pattern_set_filter etc we may want applied
417 cairo_matrix_t matrix;
418 cairo_matrix_init_translate(&matrix, rTR.mnSrcX, rTR.mnSrcY);
419 cairo_pattern_set_matrix(maskpattern, &matrix);
420 cairo_mask(cr, maskpattern);
422 cairo_pattern_destroy(maskpattern);
424 releaseCairoContext(cr, false, extents);
426 return true;
429 bool SvpSalGraphics::drawTransformedBitmap(
430 const basegfx::B2DPoint& rNull,
431 const basegfx::B2DPoint& rX,
432 const basegfx::B2DPoint& rY,
433 const SalBitmap& rSourceBitmap,
434 const SalBitmap* pAlphaBitmap)
436 if (pAlphaBitmap && pAlphaBitmap->GetBitCount() != 8 && pAlphaBitmap->GetBitCount() != 1)
438 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap alpha depth case: " << pAlphaBitmap->GetBitCount());
439 return false;
442 SourceHelper aSurface(rSourceBitmap);
443 cairo_surface_t* source = aSurface.getSurface();
444 if (!source)
446 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
447 return false;
450 std::unique_ptr<MaskHelper> xMask;
451 cairo_surface_t *mask = nullptr;
452 if (pAlphaBitmap)
454 xMask.reset(new MaskHelper(*pAlphaBitmap));
455 mask = xMask->getMask();
456 if (!mask)
458 SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
459 return false;
463 const Size aSize = rSourceBitmap.GetSize();
465 cairo_t* cr = getCairoContext(false);
466 clipRegion(cr);
468 // setup the image transformation
469 // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
470 const basegfx::B2DVector aXRel = rX - rNull;
471 const basegfx::B2DVector aYRel = rY - rNull;
472 cairo_matrix_t matrix;
473 cairo_matrix_init(&matrix,
474 aXRel.getX()/aSize.Width(), aXRel.getY()/aSize.Width(),
475 aYRel.getX()/aSize.Height(), aYRel.getY()/aSize.Height(),
476 rNull.getX(), rNull.getY());
478 cairo_transform(cr, &matrix);
480 cairo_rectangle(cr, 0, 0, aSize.Width(), aSize.Height());
481 basegfx::B2DRange extents = getClippedFillDamage(cr);
482 cairo_clip(cr);
484 cairo_set_source_surface(cr, source, 0, 0);
485 if (mask)
486 cairo_mask_surface(cr, mask, 0, 0);
487 else
488 cairo_paint(cr);
490 releaseCairoContext(cr, false, extents);
492 return true;
495 void SvpSalGraphics::clipRegion(cairo_t* cr)
497 RectangleVector aRectangles;
498 if (!m_aClipRegion.IsEmpty())
500 m_aClipRegion.GetRegionRectangles(aRectangles);
502 if (!aRectangles.empty())
504 for (auto const& rectangle : aRectangles)
506 cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(), rectangle.GetHeight());
508 cairo_clip(cr);
512 bool SvpSalGraphics::drawAlphaRect(long nX, long nY, long nWidth, long nHeight, sal_uInt8 nTransparency)
514 cairo_t* cr = getCairoContext(false);
515 clipRegion(cr);
517 const double fTransparency = (100 - nTransparency) * (1.0/100);
519 basegfx::B2DRange extents(0, 0, 0, 0);
521 cairo_rectangle(cr, nX, nY, nWidth, nHeight);
523 if (m_aFillColor != SALCOLOR_NONE)
525 cairo_set_source_rgba(cr, SALCOLOR_RED(m_aFillColor)/255.0,
526 SALCOLOR_GREEN(m_aFillColor)/255.0,
527 SALCOLOR_BLUE(m_aFillColor)/255.0,
528 fTransparency);
530 if (m_aLineColor == SALCOLOR_NONE)
531 extents = getClippedFillDamage(cr);
533 cairo_fill_preserve(cr);
536 if (m_aLineColor != SALCOLOR_NONE)
538 cairo_set_source_rgba(cr, SALCOLOR_RED(m_aLineColor)/255.0,
539 SALCOLOR_GREEN(m_aLineColor)/255.0,
540 SALCOLOR_BLUE(m_aLineColor)/255.0,
541 fTransparency);
543 extents = getClippedStrokeDamage(cr);
545 cairo_stroke_preserve(cr);
548 releaseCairoContext(cr, false, extents);
550 return true;
553 SvpSalGraphics::SvpSalGraphics()
554 : m_pSurface(nullptr)
555 , m_fScale(1.0)
556 , m_aLineColor(MAKE_SALCOLOR(0x00, 0x00, 0x00))
557 , m_aFillColor(MAKE_SALCOLOR(0xFF, 0xFF, 0XFF))
558 , m_ePaintMode(PaintMode::Over)
559 , m_aTextRenderImpl(*this)
563 SvpSalGraphics::~SvpSalGraphics()
567 void SvpSalGraphics::setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize)
569 m_pSurface = pSurface;
570 m_aFrameSize = rSize;
571 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
572 cairo_surface_get_device_scale(pSurface, &m_fScale, nullptr);
573 #endif
574 ResetClipRegion();
577 void SvpSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY )
579 rDPIX = rDPIY = 96;
582 sal_uInt16 SvpSalGraphics::GetBitCount() const
584 if (cairo_surface_get_content(m_pSurface) != CAIRO_CONTENT_COLOR_ALPHA)
585 return 1;
586 return 32;
589 long SvpSalGraphics::GetGraphicsWidth() const
591 return m_pSurface ? m_aFrameSize.getX() : 0;
594 void SvpSalGraphics::ResetClipRegion()
596 m_aClipRegion.SetNull();
599 bool SvpSalGraphics::setClipRegion( const vcl::Region& i_rClip )
601 m_aClipRegion = i_rClip;
602 return true;
605 void SvpSalGraphics::SetLineColor()
607 m_aLineColor = SALCOLOR_NONE;
610 void SvpSalGraphics::SetLineColor( SalColor nSalColor )
612 m_aLineColor = nSalColor;
615 void SvpSalGraphics::SetFillColor()
617 m_aFillColor = SALCOLOR_NONE;
620 void SvpSalGraphics::SetFillColor( SalColor nSalColor )
622 m_aFillColor = nSalColor;
625 void SvpSalGraphics::SetXORMode(bool bSet )
627 m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over;
630 void SvpSalGraphics::SetROPLineColor( SalROPColor nROPColor )
632 switch( nROPColor )
634 case SalROPColor::N0:
635 m_aLineColor = MAKE_SALCOLOR(0, 0, 0);
636 break;
637 case SalROPColor::N1:
638 m_aLineColor = MAKE_SALCOLOR(0xff, 0xff, 0xff);
639 break;
640 case SalROPColor::Invert:
641 m_aLineColor = MAKE_SALCOLOR(0xff, 0xff, 0xff);
642 break;
646 void SvpSalGraphics::SetROPFillColor( SalROPColor nROPColor )
648 switch( nROPColor )
650 case SalROPColor::N0:
651 m_aFillColor = MAKE_SALCOLOR(0, 0, 0);
652 break;
653 case SalROPColor::N1:
654 m_aFillColor = MAKE_SALCOLOR(0xff, 0xff, 0xff);
655 break;
656 case SalROPColor::Invert:
657 m_aFillColor = MAKE_SALCOLOR(0xff, 0xff, 0xff);
658 break;
662 void SvpSalGraphics::drawPixel( long nX, long nY )
664 if (m_aLineColor != SALCOLOR_NONE)
666 drawPixel(nX, nY, m_aLineColor);
670 void SvpSalGraphics::drawPixel( long nX, long nY, SalColor nSalColor )
672 SalColor aOrigFillColor = m_aFillColor;
673 SalColor aOrigLineColor = m_aLineColor;
675 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+1, nY+1));
676 m_aLineColor = SALCOLOR_NONE;
677 m_aFillColor = nSalColor;
679 drawPolyPolygon(basegfx::B2DPolyPolygon(aRect));
681 m_aFillColor = aOrigFillColor;
682 m_aLineColor = aOrigLineColor;
685 void SvpSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight )
687 // because of the -1 hack we have to do fill and draw separately
688 SalColor aOrigFillColor = m_aFillColor;
689 SalColor aOrigLineColor = m_aLineColor;
690 m_aFillColor = SALCOLOR_NONE;
691 m_aLineColor = SALCOLOR_NONE;
693 if (aOrigFillColor != SALCOLOR_NONE)
695 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight));
696 m_aFillColor = aOrigFillColor;
697 drawPolyPolygon(basegfx::B2DPolyPolygon(aRect));
698 m_aFillColor = SALCOLOR_NONE;
701 if (aOrigLineColor != SALCOLOR_NONE)
703 // need same -1 hack as X11SalGraphicsImpl::drawRect
704 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle( nX, nY, nX+nWidth-1, nY+nHeight-1));
705 m_aLineColor = aOrigLineColor;
706 drawPolyPolygon(basegfx::B2DPolyPolygon(aRect));
707 m_aLineColor = SALCOLOR_NONE;
710 m_aFillColor = aOrigFillColor;
711 m_aLineColor = aOrigLineColor;
714 void SvpSalGraphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry)
716 basegfx::B2DPolygon aPoly;
717 aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
718 for (sal_uInt32 i = 1; i < nPoints; ++i)
719 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
720 aPoly.setClosed(false);
722 drawPolyLine(aPoly, 0.0, basegfx::B2DVector(1.0, 1.0), basegfx::B2DLineJoin::Miter,
723 css::drawing::LineCap_BUTT, 15.0 * F_PI180 /*default*/);
726 void SvpSalGraphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
728 basegfx::B2DPolygon aPoly;
729 aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
730 for (sal_uInt32 i = 1; i < nPoints; ++i)
731 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
733 drawPolyPolygon(basegfx::B2DPolyPolygon(aPoly));
736 void SvpSalGraphics::drawPolyPolygon(sal_uInt32 nPoly,
737 const sal_uInt32* pPointCounts,
738 PCONSTSALPOINT* pPtAry)
740 basegfx::B2DPolyPolygon aPolyPoly;
741 for(sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
743 sal_uInt32 nPoints = pPointCounts[nPolygon];
744 if (nPoints)
746 PCONSTSALPOINT pPoints = pPtAry[nPolygon];
747 basegfx::B2DPolygon aPoly;
748 aPoly.append( basegfx::B2DPoint(pPoints->mnX, pPoints->mnY), nPoints);
749 for (sal_uInt32 i = 1; i < nPoints; ++i)
750 aPoly.setB2DPoint(i, basegfx::B2DPoint( pPoints[i].mnX, pPoints[i].mnY));
752 aPolyPoly.append(aPoly);
756 drawPolyPolygon(aPolyPoly);
759 static const basegfx::B2DPoint aHalfPointOfs(0.5, 0.5);
761 static void AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, bool bClosePath,
762 bool bPixelSnap, bool bLineDraw)
764 // short circuit if there is nothing to do
765 const int nPointCount = rPolygon.count();
766 if( nPointCount <= 0 )
768 return;
771 const bool bHasCurves = rPolygon.areControlPointsUsed();
772 basegfx::B2DPoint aLast;
774 for( int nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++ )
776 int nClosedIdx = nPointIdx;
777 if( nPointIdx >= nPointCount )
779 // prepare to close last curve segment if needed
780 if( bClosePath && (nPointIdx == nPointCount) )
782 nClosedIdx = 0;
784 else
786 break;
790 basegfx::B2DPoint aPoint = rPolygon.getB2DPoint( nClosedIdx );
792 if( bPixelSnap)
794 // snap device coordinates to full pixels
795 aPoint.setX( basegfx::fround( aPoint.getX() ) );
796 aPoint.setY( basegfx::fround( aPoint.getY() ) );
799 if( bLineDraw )
801 aPoint += aHalfPointOfs;
804 if( !nPointIdx )
806 // first point => just move there
807 cairo_move_to(cr, aPoint.getX(), aPoint.getY());
808 aLast = aPoint;
809 continue;
812 bool bPendingCurve = false;
813 if( bHasCurves )
815 bPendingCurve = rPolygon.isNextControlPointUsed( nPrevIdx );
816 bPendingCurve |= rPolygon.isPrevControlPointUsed( nClosedIdx );
819 if( !bPendingCurve ) // line segment
821 cairo_line_to(cr, aPoint.getX(), aPoint.getY());
823 else // cubic bezier segment
825 basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint( nPrevIdx );
826 basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint( nClosedIdx );
827 if( bLineDraw )
829 aCP1 += aHalfPointOfs;
830 aCP2 += aHalfPointOfs;
833 // tdf#99165 if the control points are 'empty', create the mathematical
834 // correct replacement ones to avoid problems with the graphical sub-system
835 // tdf#101026 The 1st attempt to create a mathematically correct replacement control
836 // vector was wrong. Best alternative is one as close as possible which means short.
837 if (aCP1.equal(aLast))
839 aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
842 if(aCP2.equal(aPoint))
844 aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005);
847 cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(),
848 aPoint.getX(), aPoint.getY());
851 aLast = aPoint;
854 if( bClosePath )
856 cairo_close_path(cr);
860 void SvpSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
862 basegfx::B2DPolygon aPoly;
863 aPoly.append(basegfx::B2DPoint(nX1, nY1), 2);
864 aPoly.setB2DPoint(1, basegfx::B2DPoint(nX2, nY2));
865 aPoly.setClosed(false);
867 cairo_t* cr = getCairoContext(false);
868 clipRegion(cr);
870 AddPolygonToPath(cr, aPoly, aPoly.isClosed(), !getAntiAliasB2DDraw(), true);
872 applyColor(cr, m_aLineColor);
874 basegfx::B2DRange extents = getClippedStrokeDamage(cr);
876 cairo_stroke(cr);
878 releaseCairoContext(cr, false, extents);
881 bool SvpSalGraphics::drawPolyLine(
882 const basegfx::B2DPolygon& rPolyLine,
883 double fTransparency,
884 const basegfx::B2DVector& rLineWidths,
885 basegfx::B2DLineJoin eLineJoin,
886 css::drawing::LineCap eLineCap,
887 double fMiterMinimumAngle)
889 // short circuit if there is nothing to do
890 const int nPointCount = rPolyLine.count();
891 if (nPointCount <= 0)
893 return true;
896 const bool bNoJoin = (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(rLineWidths.getX(), 0.0));
898 cairo_t* cr = getCairoContext(false);
899 clipRegion(cr);
901 // setup line attributes
902 cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
903 switch (eLineJoin)
905 case basegfx::B2DLineJoin::Bevel:
906 eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
907 break;
908 case basegfx::B2DLineJoin::Round:
909 eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
910 break;
911 case basegfx::B2DLineJoin::NONE:
912 case basegfx::B2DLineJoin::Miter:
913 eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
914 break;
917 // convert miter minimum angle to miter limit
918 double fMiterLimit = 1.0 / sin( fMiterMinimumAngle / 2.0);
920 // setup cap attribute
921 cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
923 switch (eLineCap)
925 default: // css::drawing::LineCap_BUTT:
927 eCairoLineCap = CAIRO_LINE_CAP_BUTT;
928 break;
930 case css::drawing::LineCap_ROUND:
932 eCairoLineCap = CAIRO_LINE_CAP_ROUND;
933 break;
935 case css::drawing::LineCap_SQUARE:
937 eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
938 break;
942 cairo_set_source_rgba(cr, SALCOLOR_RED(m_aLineColor)/255.0,
943 SALCOLOR_GREEN(m_aLineColor)/255.0,
944 SALCOLOR_BLUE(m_aLineColor)/255.0,
945 1.0-fTransparency);
947 cairo_set_line_join(cr, eCairoLineJoin);
948 cairo_set_line_cap(cr, eCairoLineCap);
949 cairo_set_line_width(cr, rLineWidths.getX());
950 cairo_set_miter_limit(cr, fMiterLimit);
953 basegfx::B2DRange extents(0, 0, 0, 0);
955 if (!bNoJoin)
957 AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !getAntiAliasB2DDraw(), true);
958 extents = getClippedStrokeDamage(cr);
959 cairo_stroke(cr);
961 else
963 // emulate rendering::PathJoinType::NONE by painting single edges
964 const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1);
965 basegfx::B2DPolygon aEdge;
966 aEdge.append(rPolyLine.getB2DPoint(0));
967 aEdge.append(basegfx::B2DPoint(0.0, 0.0));
969 for (sal_uInt32 i = 0; i < nEdgeCount; ++i)
971 const sal_uInt32 nNextIndex((i + 1) % nPointCount);
972 aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex));
973 aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i % nPointCount));
974 aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex));
976 AddPolygonToPath(cr, aEdge, false, !getAntiAliasB2DDraw(), true);
978 extents.expand(getStrokeDamage(cr));
980 cairo_stroke(cr);
982 // prepare next step
983 aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
986 extents.intersect(getClipBox(cr));
989 releaseCairoContext(cr, false, extents);
991 return true;
994 bool SvpSalGraphics::drawPolyLineBezier( sal_uInt32,
995 const SalPoint*,
996 const PolyFlags* )
998 SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyLineBezier case");
999 return false;
1002 bool SvpSalGraphics::drawPolygonBezier( sal_uInt32,
1003 const SalPoint*,
1004 const PolyFlags* )
1006 SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolygonBezier case");
1007 return false;
1010 bool SvpSalGraphics::drawPolyPolygonBezier( sal_uInt32,
1011 const sal_uInt32*,
1012 const SalPoint* const*,
1013 const PolyFlags* const* )
1015 SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyPolygonBezier case");
1016 return false;
1019 void SvpSalGraphics::setupPolyPolygon(cairo_t* cr, const basegfx::B2DPolyPolygon& rPolyPoly)
1021 clipRegion(cr);
1023 for (const auto & rPoly : rPolyPoly)
1024 AddPolygonToPath(cr, rPoly, true, !getAntiAliasB2DDraw(), m_aLineColor != SALCOLOR_NONE);
1027 bool SvpSalGraphics::drawPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPoly, double fTransparency)
1029 cairo_t* cr = getCairoContext(true);
1031 setupPolyPolygon(cr, rPolyPoly);
1033 basegfx::B2DRange extents(0, 0, 0, 0);
1035 if (m_aFillColor != SALCOLOR_NONE)
1037 cairo_set_source_rgba(cr, SALCOLOR_RED(m_aFillColor)/255.0,
1038 SALCOLOR_GREEN(m_aFillColor)/255.0,
1039 SALCOLOR_BLUE(m_aFillColor)/255.0,
1040 1.0-fTransparency);
1042 if (m_aLineColor == SALCOLOR_NONE)
1043 extents = getClippedFillDamage(cr);
1045 cairo_fill_preserve(cr);
1048 if (m_aLineColor != SALCOLOR_NONE)
1050 cairo_set_source_rgba(cr, SALCOLOR_RED(m_aLineColor)/255.0,
1051 SALCOLOR_GREEN(m_aLineColor)/255.0,
1052 SALCOLOR_BLUE(m_aLineColor)/255.0,
1053 1.0-fTransparency);
1055 extents = getClippedStrokeDamage(cr);
1057 cairo_stroke_preserve(cr);
1060 releaseCairoContext(cr, true, extents);
1062 return true;
1065 void SvpSalGraphics::applyColor(cairo_t *cr, SalColor aColor)
1067 if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
1069 cairo_set_source_rgba(cr, SALCOLOR_RED(aColor)/255.0,
1070 SALCOLOR_GREEN(aColor)/255.0,
1071 SALCOLOR_BLUE(aColor)/255.0,
1072 1.0);
1074 else
1076 double fSet = aColor == sal_uInt32(COL_BLACK) ? 1.0 : 0.0;
1077 cairo_set_source_rgba(cr, 1, 1, 1, fSet);
1078 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1082 void SvpSalGraphics::drawPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPoly)
1084 cairo_t* cr = getCairoContext(true);
1086 setupPolyPolygon(cr, rPolyPoly);
1088 basegfx::B2DRange extents(0, 0, 0, 0);
1090 if (m_aFillColor != SALCOLOR_NONE)
1092 applyColor(cr, m_aFillColor);
1093 if (m_aLineColor == SALCOLOR_NONE)
1094 extents = getClippedFillDamage(cr);
1095 cairo_fill_preserve(cr);
1098 if (m_aLineColor != SALCOLOR_NONE)
1100 applyColor(cr, m_aLineColor);
1101 extents = getClippedStrokeDamage(cr);
1102 cairo_stroke_preserve(cr);
1105 releaseCairoContext(cr, true, extents);
1108 void SvpSalGraphics::copyArea( long nDestX,
1109 long nDestY,
1110 long nSrcX,
1111 long nSrcY,
1112 long nSrcWidth,
1113 long nSrcHeight,
1114 bool /*bWindowInvalidate*/ )
1116 SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
1117 copyBits(aTR, this);
1120 static basegfx::B2DRange renderSource(cairo_t* cr, const SalTwoRect& rTR,
1121 cairo_surface_t* source)
1123 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1125 basegfx::B2DRange extents = getClippedFillDamage(cr);
1127 cairo_clip(cr);
1129 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1130 double fXScale = 1.0f;
1131 double fYScale = 1.0f;
1132 if (rTR.mnSrcWidth != 0 && rTR.mnSrcHeight != 0) {
1133 fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth;
1134 fYScale = static_cast<double>(rTR.mnDestHeight)/rTR.mnSrcHeight;
1135 cairo_scale(cr, fXScale, fYScale);
1138 cairo_save(cr);
1139 cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
1140 if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
1142 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1143 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
1144 cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
1146 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1147 cairo_paint(cr);
1148 cairo_restore(cr);
1150 return extents;
1153 void SvpSalGraphics::copySource( const SalTwoRect& rTR,
1154 cairo_surface_t* source )
1156 cairo_t* cr = getCairoContext(false);
1157 clipRegion(cr);
1159 basegfx::B2DRange extents = renderSource(cr, rTR, source);
1161 releaseCairoContext(cr, false, extents);
1164 void SvpSalGraphics::copyBits( const SalTwoRect& rTR,
1165 SalGraphics* pSrcGraphics )
1167 SalTwoRect aTR(rTR);
1169 SvpSalGraphics* pSrc = pSrcGraphics ?
1170 static_cast<SvpSalGraphics*>(pSrcGraphics) : this;
1172 cairo_surface_t* source = pSrc->m_pSurface;
1174 cairo_surface_t *pCopy = nullptr;
1175 if (pSrc == this)
1177 //self copy is a problem, so dup source in that case
1178 pCopy = cairo_surface_create_similar(source,
1179 cairo_surface_get_content(m_pSurface),
1180 aTR.mnSrcWidth * m_fScale,
1181 aTR.mnSrcHeight * m_fScale);
1182 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
1183 cairo_surface_set_device_scale(pCopy, m_fScale, m_fScale);
1184 #endif
1185 cairo_t* cr = cairo_create(pCopy);
1186 cairo_set_source_surface(cr, source, -aTR.mnSrcX, -aTR.mnSrcY);
1187 cairo_rectangle(cr, 0, 0, aTR.mnSrcWidth, aTR.mnSrcHeight);
1188 cairo_fill(cr);
1189 cairo_destroy(cr);
1191 source = pCopy;
1193 aTR.mnSrcX = 0;
1194 aTR.mnSrcY = 0;
1197 copySource(aTR, source);
1199 if (pCopy)
1200 cairo_surface_destroy(pCopy);
1203 namespace
1205 bool isBlackWhite(const SalBitmap& rBitmap)
1207 const SvpSalBitmap& rSrcBmp = static_cast<const SvpSalBitmap&>(rBitmap);
1208 const BitmapBuffer * pSourceBuffer = rSrcBmp.GetBuffer();
1209 const BitmapPalette & rPalette = pSourceBuffer->maPalette;
1211 return (
1212 rPalette.GetEntryCount() < 2 ||
1214 (rPalette.GetEntryCount() == 2 &&
1215 rPalette[0] == COL_BLACK &&
1216 rPalette[1] == COL_WHITE ) ||
1218 (rPalette.GetEntryCount() == 2 &&
1219 rPalette[1] == COL_BLACK &&
1220 rPalette[0] == COL_WHITE )
1225 void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap)
1227 if (rSourceBitmap.GetBitCount() == 1 && isBlackWhite(rSourceBitmap))
1229 // This way we draw only monochrome b/w bitmaps
1230 MaskHelper aMask(rSourceBitmap);
1231 cairo_surface_t* source = aMask.getMask();
1232 copySource(rTR, source);
1233 return;
1236 SourceHelper aSurface(rSourceBitmap);
1237 cairo_surface_t* source = aSurface.getSurface();
1238 copySource(rTR, source);
1241 void SvpSalGraphics::drawBitmap( const SalTwoRect& rTR,
1242 const SalBitmap& rSourceBitmap,
1243 const SalBitmap& rTransparentBitmap )
1245 drawAlphaBitmap(rTR, rSourceBitmap, rTransparentBitmap);
1248 static sal_uInt8 unpremultiply(sal_uInt8 c, sal_uInt8 a)
1250 return (a > 0) ? (c * 255 + a / 2) / a : 0;
1253 static sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a)
1255 return (c * a + 127) / 255;
1258 void SvpSalGraphics::drawMask( const SalTwoRect& rTR,
1259 const SalBitmap& rSalBitmap,
1260 SalColor nMaskColor )
1262 /** creates an image from the given rectangle, replacing all black pixels
1263 * with nMaskColor and make all other full transparent */
1264 SourceHelper aSurface(rSalBitmap, true); // The mask is argb32
1265 sal_Int32 nStride;
1266 unsigned char *mask_data = aSurface.getBits(nStride);
1267 for (sal_Int32 y = rTR.mnSrcY ; y < rTR.mnSrcY + rTR.mnSrcHeight; ++y)
1269 unsigned char *row = mask_data + (nStride*y);
1270 unsigned char *data = row + (rTR.mnSrcX * 4);
1271 for (sal_Int32 x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x)
1273 sal_uInt8 b = unpremultiply(data[SVP_CAIRO_BLUE], data[SVP_CAIRO_ALPHA]);
1274 sal_uInt8 g = unpremultiply(data[SVP_CAIRO_GREEN], data[SVP_CAIRO_ALPHA]);
1275 sal_uInt8 r = unpremultiply(data[SVP_CAIRO_RED], data[SVP_CAIRO_ALPHA]);
1276 if (r == 0 && g == 0 && b == 0)
1278 data[0] = SALCOLOR_BLUE(nMaskColor);
1279 data[1] = SALCOLOR_GREEN(nMaskColor);
1280 data[2] = SALCOLOR_RED(nMaskColor);
1281 data[3] = 0xff;
1283 else
1285 data[0] = 0;
1286 data[1] = 0;
1287 data[2] = 0;
1288 data[3] = 0;
1290 data+=4;
1293 aSurface.mark_dirty();
1295 cairo_t* cr = getCairoContext(false);
1296 clipRegion(cr);
1298 cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
1300 basegfx::B2DRange extents = getClippedFillDamage(cr);
1302 cairo_clip(cr);
1304 cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
1305 double fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth;
1306 double fYScale = static_cast<double>(rTR.mnDestHeight)/rTR.mnSrcHeight;
1307 cairo_scale(cr, fXScale, fYScale);
1308 cairo_set_source_surface(cr, aSurface.getSurface(), -rTR.mnSrcX, -rTR.mnSrcY);
1309 if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
1311 cairo_pattern_t* sourcepattern = cairo_get_source(cr);
1312 cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
1313 cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
1315 cairo_paint(cr);
1317 releaseCairoContext(cr, false, extents);
1320 SalBitmap* SvpSalGraphics::getBitmap( long nX, long nY, long nWidth, long nHeight )
1322 SvpSalBitmap* pBitmap = new SvpSalBitmap();
1323 BitmapPalette aPal;
1324 if (GetBitCount() == 1)
1326 aPal.SetEntryCount(2);
1327 aPal[0] = COL_BLACK;
1328 aPal[1] = COL_WHITE;
1331 if (!pBitmap->Create(Size(nWidth, nHeight), GetBitCount(), aPal))
1333 SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create bitmap");
1334 delete pBitmap;
1335 return nullptr;
1338 cairo_surface_t* target = SvpSalGraphics::createCairoSurface(pBitmap->GetBuffer());
1339 cairo_t* cr = cairo_create(target);
1341 SalTwoRect aTR(nX, nY, nWidth, nHeight, 0, 0, nWidth, nHeight);
1342 renderSource(cr, aTR, m_pSurface);
1344 cairo_destroy(cr);
1345 cairo_surface_destroy(target);
1347 Toggle1BitTransparency(*pBitmap->GetBuffer());
1349 return pBitmap;
1352 SalColor SvpSalGraphics::getPixel( long nX, long nY )
1354 cairo_surface_t *target = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
1355 cairo_t* cr = cairo_create(target);
1357 cairo_rectangle(cr, 0, 0, 1, 1);
1358 cairo_set_source_surface(cr, m_pSurface, -nX, -nY);
1359 cairo_paint(cr);
1360 cairo_destroy(cr);
1362 cairo_surface_flush(target);
1363 unsigned char *data = cairo_image_surface_get_data(target);
1364 sal_uInt8 b = unpremultiply(data[SVP_CAIRO_BLUE], data[SVP_CAIRO_ALPHA]);
1365 sal_uInt8 g = unpremultiply(data[SVP_CAIRO_GREEN], data[SVP_CAIRO_ALPHA]);
1366 sal_uInt8 r = unpremultiply(data[SVP_CAIRO_RED], data[SVP_CAIRO_ALPHA]);
1367 SalColor nRet = MAKE_SALCOLOR(r, g, b);
1369 cairo_surface_destroy(target);
1371 return nRet;
1374 namespace
1376 cairo_pattern_t * create_stipple()
1378 static unsigned char data[16] = { 0xFF, 0xFF, 0x00, 0x00,
1379 0xFF, 0xFF, 0x00, 0x00,
1380 0x00, 0x00, 0xFF, 0xFF,
1381 0x00, 0x00, 0xFF, 0xFF };
1382 cairo_surface_t* surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_A8, 4, 4, 4);
1383 cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface);
1384 cairo_surface_destroy(surface);
1385 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
1386 cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
1387 return pattern;
1391 void SvpSalGraphics::invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags)
1393 cairo_t* cr = getCairoContext(false);
1394 clipRegion(cr);
1396 basegfx::B2DRange extents(0, 0, 0, 0);
1398 AddPolygonToPath(cr, rPoly, true, !getAntiAliasB2DDraw(), false);
1400 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
1402 if (cairo_version() >= CAIRO_VERSION_ENCODE(1, 10, 0))
1404 cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
1406 else
1408 SAL_WARN("vcl.gdi", "SvpSalGraphics::invert, archaic cairo");
1411 if (nFlags & SalInvert::TrackFrame)
1413 cairo_set_line_width(cr, 2.0);
1414 const double dashLengths[2] = { 4.0, 4.0 };
1415 cairo_set_dash(cr, dashLengths, 2, 0);
1417 extents = getClippedStrokeDamage(cr);
1418 //see tdf#106577 under wayland, some pixel droppings seen, maybe we're
1419 //out by one somewhere, or cairo_stroke_extents is confused by
1420 //dashes/line width
1421 extents.grow(1);
1423 cairo_stroke(cr);
1425 else
1427 extents = getClippedFillDamage(cr);
1429 cairo_clip(cr);
1431 if (nFlags & SalInvert::N50)
1433 cairo_pattern_t *pattern = create_stipple();
1434 cairo_surface_t* surface = cairo_surface_create_similar(m_pSurface,
1435 cairo_surface_get_content(m_pSurface),
1436 extents.getWidth() * m_fScale,
1437 extents.getHeight() * m_fScale);
1439 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
1440 cairo_surface_set_device_scale(surface, m_fScale, m_fScale);
1441 #endif
1442 cairo_t* stipple_cr = cairo_create(surface);
1443 cairo_set_source_rgb(stipple_cr, 1.0, 1.0, 1.0);
1444 cairo_mask(stipple_cr, pattern);
1445 cairo_pattern_destroy(pattern);
1446 cairo_destroy(stipple_cr);
1447 cairo_mask_surface(cr, surface, extents.getMinX(), extents.getMinY());
1448 cairo_surface_destroy(surface);
1450 else
1452 cairo_paint(cr);
1456 releaseCairoContext(cr, false, extents);
1459 void SvpSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags )
1461 basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight));
1463 invert(aRect, nFlags);
1466 void SvpSalGraphics::invert(sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags)
1468 basegfx::B2DPolygon aPoly;
1469 aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints);
1470 for (sal_uInt32 i = 1; i < nPoints; ++i)
1471 aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
1472 aPoly.setClosed(true);
1474 invert(aPoly, nFlags);
1477 bool SvpSalGraphics::drawEPS( long, long, long, long, void*, sal_uLong )
1479 return false;
1482 namespace
1484 bool isCairoCompatible(const BitmapBuffer* pBuffer)
1486 if (!pBuffer)
1487 return false;
1489 // We use Cairo that supports 24-bit RGB.
1490 #ifdef HAVE_CAIRO_FORMAT_RGB24_888
1491 if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 24 && pBuffer->mnBitCount != 1)
1492 #else
1493 if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 1)
1494 #endif
1495 return false;
1497 cairo_format_t nFormat = getCairoFormat(*pBuffer);
1498 return (cairo_format_stride_for_width(nFormat, pBuffer->mnWidth) == pBuffer->mnScanlineSize);
1502 cairo_surface_t* SvpSalGraphics::createCairoSurface(const BitmapBuffer *pBuffer)
1504 if (!isCairoCompatible(pBuffer))
1505 return nullptr;
1507 cairo_format_t nFormat = getCairoFormat(*pBuffer);
1508 cairo_surface_t *target =
1509 cairo_image_surface_create_for_data(pBuffer->mpBits,
1510 nFormat,
1511 pBuffer->mnWidth, pBuffer->mnHeight,
1512 pBuffer->mnScanlineSize);
1513 return target;
1516 cairo_t* SvpSalGraphics::createTmpCompatibleCairoContext() const
1518 cairo_surface_t *target = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1519 m_aFrameSize.getX() * m_fScale,
1520 m_aFrameSize.getY() * m_fScale);
1521 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0)
1522 cairo_surface_set_device_scale(target, m_fScale, m_fScale);
1523 #endif
1525 return cairo_create(target);
1528 cairo_t* SvpSalGraphics::getCairoContext(bool bXorModeAllowed) const
1530 cairo_t* cr;
1531 if (m_ePaintMode == PaintMode::Xor && bXorModeAllowed)
1532 cr = createTmpCompatibleCairoContext();
1533 else
1534 cr = cairo_create(m_pSurface);
1535 cairo_set_line_width(cr, 1);
1536 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1537 cairo_set_antialias(cr, getAntiAliasB2DDraw() ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
1538 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1539 return cr;
1542 cairo_user_data_key_t* SvpSalGraphics::getDamageKey()
1544 static cairo_user_data_key_t aDamageKey;
1545 return &aDamageKey;
1548 void SvpSalGraphics::releaseCairoContext(cairo_t* cr, bool bXorModeAllowed, const basegfx::B2DRange& rExtents) const
1550 const bool bXoring = (m_ePaintMode == PaintMode::Xor && bXorModeAllowed);
1552 if (rExtents.isEmpty())
1554 //nothing changed, return early
1555 if (bXoring)
1557 cairo_surface_t* surface = cairo_get_target(cr);
1558 cairo_surface_destroy(surface);
1560 cairo_destroy(cr);
1561 return;
1564 sal_Int32 nExtentsLeft(rExtents.getMinX()), nExtentsTop(rExtents.getMinY());
1565 sal_Int32 nExtentsRight(rExtents.getMaxX()), nExtentsBottom(rExtents.getMaxY());
1566 sal_Int32 nWidth = m_aFrameSize.getX();
1567 sal_Int32 nHeight = m_aFrameSize.getY();
1568 nExtentsLeft = std::max<sal_Int32>(nExtentsLeft, 0);
1569 nExtentsTop = std::max<sal_Int32>(nExtentsTop, 0);
1570 nExtentsRight = std::min<sal_Int32>(nExtentsRight, nWidth);
1571 nExtentsBottom = std::min<sal_Int32>(nExtentsBottom, nHeight);
1573 cairo_surface_t* surface = cairo_get_target(cr);
1574 cairo_surface_flush(surface);
1576 //For the most part we avoid the use of XOR these days, but there
1577 //are some edge cases where legacy stuff still supports it, so
1578 //emulate it (slowly) here.
1579 if (bXoring)
1581 cairo_surface_t* target_surface = m_pSurface;
1582 if (cairo_surface_get_type(target_surface) != CAIRO_SURFACE_TYPE_IMAGE)
1584 //in the unlikely case we can't use m_pSurface directly, copy contents
1585 //to another temp image surface
1586 cairo_t* copycr = createTmpCompatibleCairoContext();
1587 cairo_rectangle(copycr, nExtentsLeft, nExtentsTop,
1588 nExtentsRight - nExtentsLeft,
1589 nExtentsBottom - nExtentsTop);
1590 cairo_set_source_surface(copycr, m_pSurface, 0, 0);
1591 cairo_paint(copycr);
1592 target_surface = cairo_get_target(copycr);
1593 cairo_destroy(copycr);
1596 cairo_surface_flush(target_surface);
1597 unsigned char *target_surface_data = cairo_image_surface_get_data(target_surface);
1598 unsigned char *xor_surface_data = cairo_image_surface_get_data(surface);
1600 cairo_format_t nFormat = cairo_image_surface_get_format(target_surface);
1601 assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here");
1602 sal_Int32 nStride = cairo_format_stride_for_width(nFormat, nWidth * m_fScale);
1603 sal_Int32 nUnscaledExtentsLeft = nExtentsLeft * m_fScale;
1604 sal_Int32 nUnscaledExtentsRight = nExtentsRight * m_fScale;
1605 sal_Int32 nUnscaledExtentsTop = nExtentsTop * m_fScale;
1606 sal_Int32 nUnscaledExtentsBottom = nExtentsBottom * m_fScale;
1607 for (sal_Int32 y = nUnscaledExtentsTop; y < nUnscaledExtentsBottom; ++y)
1609 unsigned char *true_row = target_surface_data + (nStride*y);
1610 unsigned char *xor_row = xor_surface_data + (nStride*y);
1611 unsigned char *true_data = true_row + (nUnscaledExtentsLeft * 4);
1612 unsigned char *xor_data = xor_row + (nUnscaledExtentsLeft * 4);
1613 for (sal_Int32 x = nUnscaledExtentsLeft; x < nUnscaledExtentsRight; ++x)
1615 sal_uInt8 b = unpremultiply(true_data[SVP_CAIRO_BLUE], true_data[SVP_CAIRO_ALPHA]) ^
1616 unpremultiply(xor_data[SVP_CAIRO_BLUE], xor_data[SVP_CAIRO_ALPHA]);
1617 sal_uInt8 g = unpremultiply(true_data[SVP_CAIRO_GREEN], true_data[SVP_CAIRO_ALPHA]) ^
1618 unpremultiply(xor_data[SVP_CAIRO_GREEN], xor_data[SVP_CAIRO_ALPHA]);
1619 sal_uInt8 r = unpremultiply(true_data[SVP_CAIRO_RED], true_data[SVP_CAIRO_ALPHA]) ^
1620 unpremultiply(xor_data[SVP_CAIRO_RED], xor_data[SVP_CAIRO_ALPHA]);
1621 true_data[0] = premultiply(b, true_data[SVP_CAIRO_ALPHA]);
1622 true_data[1] = premultiply(g, true_data[SVP_CAIRO_ALPHA]);
1623 true_data[2] = premultiply(r, true_data[SVP_CAIRO_ALPHA]);
1624 true_data+=4;
1625 xor_data+=4;
1628 cairo_surface_mark_dirty(target_surface);
1630 if (target_surface != m_pSurface)
1632 cairo_t* copycr = cairo_create(m_pSurface);
1633 //unlikely case we couldn't use m_pSurface directly, copy contents
1634 //back from image surface
1635 cairo_rectangle(copycr, nExtentsLeft, nExtentsTop,
1636 nExtentsRight - nExtentsLeft,
1637 nExtentsBottom - nExtentsTop);
1638 cairo_set_source_surface(copycr, target_surface, 0, 0);
1639 cairo_paint(copycr);
1640 cairo_destroy(copycr);
1641 cairo_surface_destroy(target_surface);
1644 cairo_surface_destroy(surface);
1647 cairo_destroy(cr); // unref
1649 DamageHandler* pDamage = static_cast<DamageHandler*>(cairo_surface_get_user_data(m_pSurface, getDamageKey()));
1651 if (pDamage)
1653 pDamage->damaged(pDamage->handle, nExtentsLeft, nExtentsTop,
1654 nExtentsRight - nExtentsLeft,
1655 nExtentsBottom - nExtentsTop);
1659 #if ENABLE_CAIRO_CANVAS
1660 bool SvpSalGraphics::SupportsCairo() const
1662 return false;
1665 cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
1667 return cairo::SurfaceSharedPtr();
1670 cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/) const
1672 return cairo::SurfaceSharedPtr();
1675 cairo::SurfaceSharedPtr SvpSalGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/, const BitmapSystemData& /*rData*/, const Size& /*rSize*/) const
1677 return cairo::SurfaceSharedPtr();
1680 css::uno::Any SvpSalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/, const basegfx::B2ISize& /*rSize*/) const
1682 return css::uno::Any();
1685 #endif // ENABLE_CAIRO_CANVAS
1687 SystemGraphicsData SvpSalGraphics::GetGraphicsData() const
1689 return SystemGraphicsData();
1692 bool SvpSalGraphics::supportsOperation(OutDevSupportType eType) const
1694 switch (eType)
1696 case OutDevSupportType::TransparentRect:
1697 case OutDevSupportType::B2DDraw:
1698 return true;
1700 return false;
1703 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */