/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
// Remove bClosePath: Checked that the already used mechanism for Win using // Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace // this. // For PixelSnap we need the ObjectToDevice transformation here now. This is a // special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in // DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we // need the ObjectToDevice transformation *without* that offset here to do the // same. The LineDraw-Offset will be applied by the callers using a linear // transformation for Cairo now // For support of PixelSnapHairline we also need the ObjectToDevice transformation // and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g. // for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!) // tdf#129845 add reply value to allow counting a point/byte/size measurement to // be included static size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap, bool bPixelSnapHairline)
{ // short circuit if there is nothing to do const sal_uInt32 nPointCount(rPolygon.count());
size_t nSizeMeasure(0);
if (bPixelSnap)
{ // snap device coordinates to full pixels if (bObjectToDeviceUsed)
{ // go to DeviceCoordinates
aPoint *= rObjectToDevice;
}
// snap by rounding
aPoint.setX(basegfx::fround(aPoint.getX()));
aPoint.setY(basegfx::fround(aPoint.getY()));
if (bObjectToDeviceUsed)
{ if (aObjectToDeviceInv.isIdentity())
{
aObjectToDeviceInv = rObjectToDevice;
aObjectToDeviceInv.invert();
}
// go back to ObjectCoordinates
aPoint *= aObjectToDeviceInv;
}
}
if (bPixelSnapHairline)
{ // snap horizontal and vertical lines (mainly used in Chart for // 'nicer' AAing)
aPoint = aSnapper.snap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx);
}
if (!nPointIdx)
{ // first point => just move there
cairo_move_to(cr, aPoint.getX(), aPoint.getY());
aLast = aPoint; continue;
}
bool bPendingCurve(false);
if (bHasCurves)
{
bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
}
// tdf#99165 if the control points are 'empty', create the mathematical // correct replacement ones to avoid problems with the graphical sub-system // tdf#101026 The 1st attempt to create a mathematically correct replacement control // vector was wrong. Best alternative is one as close as possible which means short. if (aCP1.equal(aLast))
{
aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
}
cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(),
aPoint.getY()); // take some bigger measure for curve segments - too expensive to subdivide // here and that precision not needed, but four (2 points, 2 control-points) // would be a too low weight
nSizeMeasure += 10;
}
// get the data if (nIndex == 0)
{ // if it's the first time, we need to calculate everything
maPrevPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount);
maCurrPoint = rObjectToDevice * rPolygon.getB2DPoint(nIndex);
maPrevTuple = basegfx::fround(maPrevPoint);
maCurrTuple = basegfx::fround(maCurrPoint);
} else
{ // but for all other times, we can re-use the previous iteration computations
maPrevPoint = maCurrPoint;
maPrevTuple = maCurrTuple;
maCurrPoint = maNextPoint;
maCurrTuple = maNextTuple;
}
maNextPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount);
maNextTuple = basegfx::fround(maNextPoint);
// get the states constbool bPrevVertical(maPrevTuple.getX() == maCurrTuple.getX()); constbool bNextVertical(maNextTuple.getX() == maCurrTuple.getX()); constbool bPrevHorizontal(maPrevTuple.getY() == maCurrTuple.getY()); constbool bNextHorizontal(maNextTuple.getY() == maCurrTuple.getY()); constbool bSnapX(bPrevVertical || bNextVertical); constbool bSnapY(bPrevHorizontal || bNextHorizontal);
// tdf#129845 only create a copy of the path when nSizeMeasure is // bigger than some decent threshold if (!bFuzzing && nSizeMeasure > 50)
{
mpCairoPath = cairo_copy_path(cr);
sal_Int64 SystemDependentData_CairoPath::estimateUsageInBytes() const
{ // tdf#129845 by using the default return value of zero when no path // was created, SystemDependentData::calculateCombinedHoldCyclesInSeconds // will do the right thing and not buffer this entry at all
sal_Int64 nRetval(0);
if (nullptr != mpCairoPath)
{ // per node // - num_data incarnations of // - sizeof(cairo_path_data_t) which is a union of defines and point data // thus may 2 x sizeof(double)
nRetval = mpCairoPath->num_data * sizeof(cairo_path_data_t);
}
if (pSystemDependentData_CairoPath)
{ // re-use data
cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
} else
{ // create data
size_t nSizeMeasure(0);
for (constauto& rPoly : rPolyPolygon)
{ // PixelOffset used: Was dependent of 'm_aLineColor != SALCOLOR_NONE' // Adapt setupPolyPolygon-users to set a linear transformation to achieve PixelOffset
nSizeMeasure += AddPolygonToPath(cr, rPoly, rObjectToDevice, bPixelSnap, false);
}
// copy and add to buffering mechanism // for decisions how/what to buffer, see Note in WinSalGraphicsImpl::drawPolyPolygon
pSystemDependentData_CairoPath
= rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
nSizeMeasure, cr, false, false, nullptr);
}
}
//For the most part we avoid the use of XOR these days, but there //are some edge cases where legacy stuff still supports it, so //emulate it (slowly) here. if (bXoring)
doXorOnRelease(nExtentsLeft, nExtentsTop, nExtentsRight, nExtentsBottom, surface, nWidth);
void CairoCommon::doXorOnRelease(sal_Int32 nExtentsLeft, sal_Int32 nExtentsTop,
sal_Int32 nExtentsRight, sal_Int32 nExtentsBottom,
cairo_surface_t* const surface, sal_Int32 nWidth) const
{ //For the most part we avoid the use of XOR these days, but there //are some edge cases where legacy stuff still supports it, so //emulate it (slowly) here.
cairo_surface_t* target_surface = m_pSurface; if (cairo_surface_get_type(target_surface) != CAIRO_SURFACE_TYPE_IMAGE)
{ //in the unlikely case we can't use m_pSurface directly, copy contents //to another temp image surface if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
target_surface = cairo_surface_map_to_image(target_surface, nullptr); else
{ // for gen, which is CAIRO_FORMAT_RGB24/CAIRO_CONTENT_COLOR I'm getting // visual corruption in vcldemo with cairo_surface_map_to_image
cairo_t* copycr = createTmpCompatibleCairoContext();
cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
nExtentsBottom - nExtentsTop);
cairo_set_source_surface(copycr, m_pSurface, 0, 0);
cairo_fill(copycr);
target_surface = cairo_get_target(copycr);
cairo_destroy(copycr);
}
}
// PixelOffset used: To not mix with possible PixelSnap, cannot do // directly on coordinates as tried before - despite being already 'snapped' // due to being integer. If it would be directly added here, it would be // 'snapped' again when !getAntiAlias(), losing the (0.5, 0.5) offset
aPoly.append(basegfx::B2DPoint(nX1, nY1));
aPoly.append(basegfx::B2DPoint(nX2, nY2));
// PixelOffset used: Set PixelOffset as linear transformation
cairo_matrix_t aMatrix;
cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
cairo_set_matrix(cr, &aMatrix);
// true if we have a fill color and the line color is the same or non-existent staticbool onlyFillRect(const std::optional<Color>& rFillColor, const std::optional<Color>& rLineColor)
{ if (!rFillColor) returnfalse; if (!rLineColor) returntrue; return *rFillColor == *rLineColor;
}
void CairoCommon::drawRect(double nX, double nY, double nWidth, double nHeight, bool bAntiAlias)
{ // fast path for the common case of simply creating a solid block of color if (onlyFillRect(m_oFillColor, m_oLineColor))
{ double fTransparency = 0; // don't bother trying to draw stuff which is effectively invisible if (nWidth < 0.1 || nHeight < 0.1) return;
bool bPixelSnap = !bAntiAlias; if (bPixelSnap)
{ // snap by rounding
nX = basegfx::fround(nX);
nY = basegfx::fround(nY);
nWidth = basegfx::fround(nWidth);
nHeight = basegfx::fround(nHeight);
}
cairo_rectangle(cr, nX, nY, nWidth, nHeight);
CairoCommon::applyColor(cr, *m_oFillColor, fTransparency); // Get FillDamage
basegfx::B2DRange extents = getClippedFillDamage(cr);
cairo_fill(cr);
releaseCairoContext(cr, true, extents);
return;
} // because of the -1 hack we have to do fill and draw separately
std::optional<Color> aOrigFillColor = m_oFillColor;
std::optional<Color> aOrigLineColor = m_oLineColor;
m_oFillColor = std::nullopt;
m_oLineColor = std::nullopt;
if (aOrigFillColor)
{
basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
basegfx::B2DRectangle(nX, nY, nX + nWidth, nY + nHeight));
if (aOrigLineColor)
{ // need -1 hack to exclude the bottom and right edges to act like wingdi "Rectangle" // function which is what was probably the ultimate origin of this behavior
basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
basegfx::B2DRectangle(nX, nY, nX + nWidth - 1, nY + nHeight - 1));
if (!bHasLine)
{ // don't bother trying to draw stuff which is effectively invisible, speeds up // drawing some complex drawings. This optimisation is not valid when we do // the pixel offset thing (i.e. bHasLine)
basegfx::B2DRange aPolygonRange = rPolyPolygon.getB2DRange();
aPolygonRange.transform(rObjectToDevice); if (aPolygonRange.getWidth() < 0.1 || aPolygonRange.getHeight() < 0.1) return;
}
// To make releaseCairoContext work, use empty extents
basegfx::B2DRange extents;
if (bHasFill)
{
add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !bAntiAlias);
CairoCommon::applyColor(cr, *m_oFillColor, fTransparency); // Get FillDamage (will be extended for LineDamage below)
extents = getClippedFillDamage(cr);
cairo_fill(cr);
}
if (bHasLine)
{ // PixelOffset used: Set PixelOffset as linear transformation
cairo_matrix_t aMatrix;
cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
cairo_set_matrix(cr, &aMatrix);
// expand with possible StrokeDamage
basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr);
stroke_extents.translate(0.5, 0.5);
extents.expand(stroke_extents);
cairo_stroke(cr);
}
// if transformation has been applied, transform also extents (ranges) // of damage so they can be correctly redrawn
extents.transform(rObjectToDevice);
releaseCairoContext(cr, true, extents);
}
void CairoCommon::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias)
{
basegfx::B2DPolygon aPoly;
aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints); for (sal_uInt32 i = 1; i < nPoints; ++i)
aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
aPoly.setClosed(false);
// need to check/handle LineWidth when ObjectToDevice transformation is used constbool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
// tdf#124848 calculate-back logical LineWidth for a hairline // since this implementation hands over the transformation to // the graphic sub-system if (fLineWidth == 0)
{
fLineWidth = 1.0;
// PixelOffset used: Need to reflect in linear transformation
cairo_matrix_t aMatrix;
basegfx::B2DHomMatrix aDamageMatrix(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
if (bObjectToDeviceIsIdentity)
{ // Set PixelOffset as requested
cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
} else
{ // Prepare ObjectToDevice transformation. Take PixelOffset for Lines into // account: Multiply from left to act in DeviceCoordinates
aDamageMatrix = aDamageMatrix * rObjectToDevice;
cairo_matrix_init(&aMatrix, aDamageMatrix.get(0, 0), aDamageMatrix.get(1, 0),
aDamageMatrix.get(0, 1), aDamageMatrix.get(1, 1), aDamageMatrix.get(0, 2),
aDamageMatrix.get(1, 2));
}
// set linear transformation
cairo_set_matrix(cr, &aMatrix);
// setup line attributes
cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER; switch (eLineJoin)
{ case basegfx::B2DLineJoin::Bevel:
eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL; break; case basegfx::B2DLineJoin::Round:
eCairoLineJoin = CAIRO_LINE_JOIN_ROUND; break; case basegfx::B2DLineJoin::NONE: case basegfx::B2DLineJoin::Miter:
eCairoLineJoin = CAIRO_LINE_JOIN_MITER; break;
}
constexpr int MaxNormalLineWidthPx = 64; if (fLineWidth > MaxNormalLineWidthPx)
{ constdouble fLineWidthPixel
= bObjectToDeviceIsIdentity
? fLineWidth
: (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
constexpr double MaxLineWidth = 0x20000000; // if the width is pixels is excessive, or if the actual number is huge, then // when fuzzing drop it to something small if (fLineWidthPixel > MaxNormalLineWidthPx || fLineWidth > MaxLineWidth)
{
SAL_WARN("vcl.gdi", "drawPolyLine, suspicious input line width of: "
<< fLineWidth << ", will be " << fLineWidthPixel
<< " pixels thick"); if (bFuzzing)
{
basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
aObjectToDeviceInv.invert();
fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(MaxNormalLineWidthPx, 0))
.getLength();
fLineWidth = std::min(fLineWidth, 2048.0);
}
}
}
cairo_set_line_width(cr, fLineWidth);
cairo_set_miter_limit(cr, fMiterLimit);
// try to access buffered data
std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>(
basegfx::SDD_Type::SDDType_CairoPath));
// MM01 need to do line dashing as fallback stuff here now constdouble fDotDashLength(
nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0); constbool bStrokeUsed(0.0 != fDotDashLength);
assert(!bStrokeUsed || (bStrokeUsed && pStroke));
// MM01 activate to stroke directly if (bStrokeUsed)
{
cairo_set_dash(cr, pStroke->data(), pStroke->size(), 0.0);
}
// check for basegfx::B2DLineJoin::NONE to react accordingly constbool bNoJoin(basegfx::B2DLineJoin::NONE == eLineJoin && fLineWidth > 0.0
&& !basegfx::fTools::equalZero(fLineWidth));
if (pSystemDependentData_CairoPath)
{ auto strokeEquals
= [](const std::vector<double>& rStroke1, const std::vector<double>* pStroke2) -> bool { if (!pStroke2) return rStroke1.empty(); return rStroke1 == *pStroke2;
}; // check data validity if (nullptr == pSystemDependentData_CairoPath->getCairoPath()
|| pSystemDependentData_CairoPath->getNoJoin() != bNoJoin
|| pSystemDependentData_CairoPath->getAntiAlias() != bAntiAlias
|| !strokeEquals(pSystemDependentData_CairoPath->getStroke(), pStroke)
|| bPixelSnapHairline /*tdf#124700*/)
{ // data invalid, forget
pSystemDependentData_CairoPath.reset();
}
}
if (pSystemDependentData_CairoPath)
{ // re-use data
cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
} else
{ // create data
size_t nSizeMeasure(0);
// MM01 need to do line dashing as fallback stuff here now
basegfx::B2DPolyPolygon aPolyPolygonLine;
// no line dashing or direct stroke, just copy
aPolyPolygonLine.append(rPolyLine);
// MM01 checked/verified for Cairo for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
{ const basegfx::B2DPolygon& aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
if (!bNoJoin)
{ // PixelOffset now reflected in linear transformation used
nSizeMeasure
+= AddPolygonToPath(cr, aPolyLine,
rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
!bAntiAlias, bPixelSnapHairline);
} else
{ const sal_uInt32 nPointCount(aPolyLine.count()); const sal_uInt32 nEdgeCount(aPolyLine.isClosed() ? nPointCount : nPointCount - 1);
basegfx::B2DPolygon aEdge;
// PixelOffset now reflected in linear transformation used
nSizeMeasure += AddPolygonToPath(
cr, aEdge,
rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
!bAntiAlias, bPixelSnapHairline);
// prepare next step
aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
}
}
}
// copy and add to buffering mechanism if (!bPixelSnapHairline /*tdf#124700*/)
{
pSystemDependentData_CairoPath
= rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
nSizeMeasure, cr, bNoJoin, bAntiAlias, pStroke);
}
}
// extract extents
basegfx::B2DRange extents = getClippedStrokeDamage(cr); // transform also extents (ranges) of damage so they can be correctly redrawn
extents.transform(aDamageMatrix);
// To make releaseCairoContext work, use empty extents
basegfx::B2DRange extents;
if (bHasFill)
{
cairo_rectangle(cr, nX, nY, nWidth, nHeight);
applyColor(cr, *m_oFillColor, fTransparency);
// set FillDamage
extents = getClippedFillDamage(cr);
cairo_fill(cr);
}
if (bHasLine)
{ // PixelOffset used: Set PixelOffset as linear transformation // Note: Was missing here - probably not by purpose (?)
cairo_matrix_t aMatrix;
cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
cairo_set_matrix(cr, &aMatrix);
cairo_rectangle(cr, nX, nY, nWidth, nHeight);
applyColor(cr, *m_oLineColor, fTransparency);
// expand with possible StrokeDamage
basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr);
stroke_extents.translate(0.5, 0.5);
extents.expand(stroke_extents);
cairo_stroke(cr);
}
releaseCairoContext(cr, false, extents);
returntrue;
}
bool CairoCommon::drawGradient(const tools::PolyPolygon& rPolyPolygon, const Gradient& rGradient, bool bAntiAlias)
{ if (rGradient.GetStyle() != css::awt::GradientStyle_LINEAR
&& rGradient.GetStyle() != css::awt::GradientStyle_RADIAL) returnfalse; // unsupported if (rGradient.GetSteps() != 0) returnfalse; // We can't tell cairo how many colors to use in the gradient.
if (cairo_status(cr) == CAIRO_STATUS_SUCCESS)
{ //tdf#133716 borders of upscaled images should not be blurred
cairo_pattern_t* sourcepattern = cairo_get_source(cr);
cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
}
extents = getClippedStrokeDamage(cr); //see tdf#106577 under wayland, some pixel droppings seen, maybe we're //out by one somewhere, or cairo_stroke_extents is confused by //dashes/line width if (!extents.isEmpty())
{
extents.grow(1);
}
if (!source)
{
SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case"); return;
}
#if 0 // LO code is not yet bitmap32-ready. // if m_bSupportsBitmap32 becomes true for Svp revisit this
copyWithOperator(rPosAry, source, CAIRO_OPERATOR_OVER, bAntiAlias); #else
copyWithOperator(rPosAry, source, CAIRO_OPERATOR_SOURCE, bAntiAlias); #endif
}
//tdf#133716 borders of upscaled images should not be blurred //tdf#114117 when stretching a single or multi pixel width/height source to fit an area //the image will be extended into that size.
cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD);
cairo_pattern_set_extend(maskpattern, CAIRO_EXTEND_PAD);
//this block is just "cairo_mask_surface", but we have to make it explicit //because of the cairo_pattern_set_filter etc we may want applied
cairo_matrix_t matrix;
cairo_matrix_init_translate(&matrix, rTR.mnSrcX, rTR.mnSrcY);
cairo_pattern_set_matrix(maskpattern, &matrix);
cairo_mask(cr, maskpattern);
void CairoCommon::drawMask(const SalTwoRect& rTR, const SalBitmap& rSalBitmap, Color nMaskColor, bool bAntiAlias)
{ /** creates an image from the given rectangle, replacing all black pixels
* with nMaskColor and make all other full transparent */ // MM02 here decided *against* using buffered BitmapHelper // because the data gets somehow 'unmuliplied'. This may also be // done just once, but I am not sure if this is safe to do. // So for now dispense re-using data here.
BitmapHelper aSurface(rSalBitmap, true); // The mask is argb32 if (!aSurface.getSurface())
{
SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawMask case"); return;
}
sal_Int32 nStride;
--> --------------------
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.