| /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ |
| /* |
| * This file is part of the Collabora Office 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 . |
| */ |
| |
| #include <sal/config.h> |
| #include <sal/log.hxx> |
| |
| #include <cassert> |
| #include <cstring> |
| #include <numeric> |
| #include <utility> |
| |
| #include <basegfx/polygon/b2dpolygon.hxx> |
| #include <basegfx/polygon/b2dpolygontools.hxx> |
| #include <basegfx/polygon/b2dpolypolygontools.hxx> |
| #include <osl/endian.h> |
| #include <osl/file.hxx> |
| #include <sal/types.h> |
| #include <tools/long.hxx> |
| #include <vcl/sysdata.hxx> |
| |
| #include <fontsubset.hxx> |
| #include <quartz/salbmp.h> |
| #ifdef MACOSX |
| #include <quartz/salgdi.h> |
| #endif |
| #include <quartz/utils.h> |
| #ifdef IOS |
| #include <ios/iosinst.hxx> |
| #endif |
| |
| using namespace vcl; |
| |
| namespace |
| { |
| const basegfx::B2DPoint aHalfPointOfs(0.5, 0.5); |
| |
| void AddPolygonToPath(CGMutablePathRef xPath, const basegfx::B2DPolygon& rPolygon, bool bClosePath, |
| bool bPixelSnap, bool bLineDraw) |
| { |
| // short circuit if there is nothing to do |
| const int nPointCount = rPolygon.count(); |
| if (nPointCount <= 0) |
| { |
| return; |
| } |
| |
| const bool bHasCurves = rPolygon.areControlPointsUsed(); |
| for (int nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++) |
| { |
| int nClosedIdx = nPointIdx; |
| if (nPointIdx >= nPointCount) |
| { |
| // prepare to close last curve segment if needed |
| if (bClosePath && (nPointIdx == nPointCount)) |
| { |
| nClosedIdx = 0; |
| } |
| else |
| { |
| break; |
| } |
| } |
| |
| basegfx::B2DPoint aPoint = rPolygon.getB2DPoint(nClosedIdx); |
| |
| if (bPixelSnap) |
| { |
| // snap device coordinates to full pixels |
| aPoint.setX(basegfx::fround(aPoint.getX())); |
| aPoint.setY(basegfx::fround(aPoint.getY())); |
| } |
| |
| if (bLineDraw) |
| { |
| aPoint += aHalfPointOfs; |
| } |
| if (!nPointIdx) |
| { |
| // first point => just move there |
| CGPathMoveToPoint(xPath, nullptr, aPoint.getX(), aPoint.getY()); |
| continue; |
| } |
| |
| bool bPendingCurve = false; |
| if (bHasCurves) |
| { |
| bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx); |
| bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx); |
| } |
| |
| if (!bPendingCurve) // line segment |
| { |
| CGPathAddLineToPoint(xPath, nullptr, aPoint.getX(), aPoint.getY()); |
| } |
| else // cubic bezier segment |
| { |
| basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx); |
| basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx); |
| if (bLineDraw) |
| { |
| aCP1 += aHalfPointOfs; |
| aCP2 += aHalfPointOfs; |
| } |
| CGPathAddCurveToPoint(xPath, nullptr, aCP1.getX(), aCP1.getY(), aCP2.getX(), |
| aCP2.getY(), aPoint.getX(), aPoint.getY()); |
| } |
| } |
| |
| if (bClosePath) |
| { |
| CGPathCloseSubpath(xPath); |
| } |
| } |
| |
| void alignLinePoint(const Point* i_pIn, float& o_fX, float& o_fY) |
| { |
| o_fX = static_cast<float>(i_pIn->getX()) + 0.5; |
| o_fY = static_cast<float>(i_pIn->getY()) + 0.5; |
| } |
| |
| void getBoundRect(sal_uInt32 nPoints, const Point* pPtAry, tools::Long& rX, tools::Long& rY, |
| tools::Long& rWidth, tools::Long& rHeight) |
| { |
| tools::Long nX1 = pPtAry->getX(); |
| tools::Long nX2 = nX1; |
| tools::Long nY1 = pPtAry->getY(); |
| tools::Long nY2 = nY1; |
| |
| for (sal_uInt32 n = 1; n < nPoints; n++) |
| { |
| if (pPtAry[n].getX() < nX1) |
| { |
| nX1 = pPtAry[n].getX(); |
| } |
| else if (pPtAry[n].getX() > nX2) |
| { |
| nX2 = pPtAry[n].getX(); |
| } |
| if (pPtAry[n].getY() < nY1) |
| { |
| nY1 = pPtAry[n].getY(); |
| } |
| else if (pPtAry[n].getY() > nY2) |
| { |
| nY2 = pPtAry[n].getY(); |
| } |
| } |
| rX = nX1; |
| rY = nY1; |
| rWidth = nX2 - nX1 + 1; |
| rHeight = nY2 - nY1 + 1; |
| } |
| |
| Color ImplGetROPColor(SalROPColor nROPColor) |
| { |
| Color nColor; |
| if (nROPColor == SalROPColor::N0) |
| { |
| nColor = Color(0, 0, 0); |
| } |
| else |
| { |
| nColor = Color(255, 255, 255); |
| } |
| return nColor; |
| } |
| |
| void drawPattern50(void*, CGContextRef rContext) |
| { |
| static const CGRect aRects[2] = { { { 0, 0 }, { 2, 2 } }, { { 2, 2 }, { 2, 2 } } }; |
| CGContextAddRects(rContext, aRects, 2); |
| CGContextFillPath(rContext); |
| } |
| } |
| |
| AquaGraphicsBackend::AquaGraphicsBackend(AquaSharedAttributes& rShared) |
| : AquaGraphicsBackendBase(rShared, this) |
| { |
| } |
| |
| AquaGraphicsBackend::~AquaGraphicsBackend() {} |
| |
| void AquaGraphicsBackend::setClipRegion(vcl::Region const& rRegion) |
| { |
| // release old clip path |
| mrShared.unsetClipPath(); |
| mrShared.mxClipPath = CGPathCreateMutable(); |
| |
| // set current path, either as polypolgon or sequence of rectangles |
| RectangleVector aRectangles; |
| rRegion.GetRegionRectangles(aRectangles); |
| |
| for (const auto& rRect : aRectangles) |
| { |
| const tools::Long nW(rRect.Right() - rRect.Left() + 1); // uses +1 logic in original |
| |
| if (nW) |
| { |
| const tools::Long nH(rRect.Bottom() - rRect.Top() + 1); // uses +1 logic in original |
| |
| if (nH) |
| { |
| const CGRect aRect = CGRectMake(rRect.Left(), rRect.Top(), nW, nH); |
| CGPathAddRect(mrShared.mxClipPath, nullptr, aRect); |
| } |
| } |
| } |
| // set the current path as clip region |
| if (mrShared.checkContext()) |
| mrShared.setState(); |
| } |
| |
| void AquaGraphicsBackend::ResetClipRegion() |
| { |
| // release old path and indicate no clipping |
| mrShared.unsetClipPath(); |
| |
| if (mrShared.checkContext()) |
| { |
| mrShared.setState(); |
| } |
| } |
| |
| sal_uInt16 AquaGraphicsBackend::GetBitCount() const |
| { |
| sal_uInt16 nBits = mrShared.mnBitmapDepth ? mrShared.mnBitmapDepth : 32; //24; |
| return nBits; |
| } |
| |
| tools::Long AquaGraphicsBackend::GetGraphicsWidth() const |
| { |
| tools::Long width = 0; |
| if (mrShared.maContextHolder.isSet() |
| && ( |
| #ifndef IOS |
| mrShared.mbWindow || |
| #endif |
| mrShared.mbVirDev)) |
| { |
| width = mrShared.mnWidth; |
| } |
| |
| #ifndef IOS |
| if (width == 0) |
| { |
| if (mrShared.mbWindow && mrShared.mpFrame) |
| { |
| width = mrShared.mpFrame->GetWidth(); |
| } |
| } |
| #endif |
| return width; |
| } |
| |
| void AquaGraphicsBackend::SetLineColor() |
| { |
| mrShared.maLineColor.SetActive(false); |
| if (mrShared.checkContext()) |
| { |
| CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), mrShared.maLineColor.GetRed(), |
| mrShared.maLineColor.GetGreen(), mrShared.maLineColor.GetBlue(), |
| 0.0); // alpha, transparent |
| } |
| } |
| |
| void AquaGraphicsBackend::SetLineColor(Color nColor) |
| { |
| mrShared.maLineColor = RGBAColor(nColor); |
| if (mrShared.checkContext()) |
| { |
| CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), mrShared.maLineColor.GetRed(), |
| mrShared.maLineColor.GetGreen(), mrShared.maLineColor.GetBlue(), |
| mrShared.maLineColor.GetAlpha()); |
| } |
| } |
| |
| void AquaGraphicsBackend::SetFillColor() |
| { |
| mrShared.maFillColor.SetActive(false); |
| if (mrShared.checkContext()) |
| { |
| CGContextSetRGBFillColor(mrShared.maContextHolder.get(), mrShared.maFillColor.GetRed(), |
| mrShared.maFillColor.GetGreen(), mrShared.maFillColor.GetBlue(), |
| 0.0); // alpha, transparent |
| } |
| } |
| |
| void AquaGraphicsBackend::SetFillColor(Color nColor) |
| { |
| mrShared.maFillColor = RGBAColor(nColor); |
| if (mrShared.checkContext()) |
| { |
| CGContextSetRGBFillColor(mrShared.maContextHolder.get(), mrShared.maFillColor.GetRed(), |
| mrShared.maFillColor.GetGreen(), mrShared.maFillColor.GetBlue(), |
| mrShared.maFillColor.GetAlpha()); |
| } |
| } |
| |
| void AquaGraphicsBackend::SetXORMode(bool bSet, bool bInvertOnly) |
| { |
| // return early if XOR mode remains unchanged |
| if (mrShared.mbPrinter) |
| { |
| return; |
| } |
| if (!bSet && mrShared.mnXorMode == 2) |
| { |
| CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeNormal); |
| mrShared.mnXorMode = 0; |
| return; |
| } |
| else if (bSet && bInvertOnly && mrShared.mnXorMode == 0) |
| { |
| CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference); |
| mrShared.mnXorMode = 2; |
| return; |
| } |
| |
| if (!mrShared.mpXorEmulation && !bSet) |
| { |
| return; |
| } |
| if (mrShared.mpXorEmulation && bSet == mrShared.mpXorEmulation->IsEnabled()) |
| { |
| return; |
| } |
| if (!mrShared.checkContext()) |
| { |
| return; |
| } |
| // prepare XOR emulation |
| if (!mrShared.mpXorEmulation) |
| { |
| mrShared.mpXorEmulation = std::make_unique<XorEmulation>(); |
| mrShared.mpXorEmulation->SetTarget(mrShared.mnWidth, mrShared.mnHeight, |
| mrShared.mnBitmapDepth, mrShared.maContextHolder.get(), |
| mrShared.maLayer.get()); |
| } |
| |
| // change the XOR mode |
| if (bSet) |
| { |
| mrShared.mpXorEmulation->Enable(); |
| mrShared.maContextHolder.set(mrShared.mpXorEmulation->GetMaskContext()); |
| mrShared.mnXorMode = 1; |
| } |
| else |
| { |
| mrShared.mpXorEmulation->UpdateTarget(); |
| mrShared.mpXorEmulation->Disable(); |
| mrShared.maContextHolder.set(mrShared.mpXorEmulation->GetTargetContext()); |
| mrShared.mnXorMode = 0; |
| } |
| } |
| |
| void AquaGraphicsBackend::SetROPFillColor(SalROPColor nROPColor) |
| { |
| if (!mrShared.mbPrinter) |
| { |
| SetFillColor(ImplGetROPColor(nROPColor)); |
| } |
| } |
| |
| void AquaGraphicsBackend::SetROPLineColor(SalROPColor nROPColor) |
| { |
| if (!mrShared.mbPrinter) |
| { |
| SetLineColor(ImplGetROPColor(nROPColor)); |
| } |
| } |
| |
| void AquaGraphicsBackend::drawPixelImpl(tools::Long nX, tools::Long nY, const RGBAColor& rColor) |
| { |
| if (!mrShared.checkContext()) |
| return; |
| |
| // overwrite the fill color |
| CGContextSetFillColor(mrShared.maContextHolder.get(), rColor.AsArray()); |
| // draw 1x1 rect, there is no pixel drawing in Quartz |
| const CGRect aDstRect = CGRectMake(nX, nY, 1, 1); |
| CGContextFillRect(mrShared.maContextHolder.get(), aDstRect); |
| |
| refreshRect(aDstRect); |
| |
| // reset the fill color |
| CGContextSetFillColor(mrShared.maContextHolder.get(), mrShared.maFillColor.AsArray()); |
| } |
| |
| void AquaGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY) |
| { |
| // draw pixel with current line color |
| drawPixelImpl(nX, nY, mrShared.maLineColor); |
| } |
| |
| void AquaGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY, Color nColor) |
| { |
| const RGBAColor aPixelColor(nColor); |
| drawPixelImpl(nX, nY, aPixelColor); |
| } |
| |
| void AquaGraphicsBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, |
| tools::Long nY2) |
| { |
| if (nX1 == nX2 && nY1 == nY2) |
| { |
| // #i109453# platform independent code expects at least one pixel to be drawn |
| drawPixel(nX1, nY1); |
| return; |
| } |
| |
| if (!mrShared.checkContext()) |
| return; |
| |
| CGContextBeginPath(mrShared.maContextHolder.get()); |
| CGContextMoveToPoint(mrShared.maContextHolder.get(), float(nX1) + 0.5, float(nY1) + 0.5); |
| CGContextAddLineToPoint(mrShared.maContextHolder.get(), float(nX2) + 0.5, float(nY2) + 0.5); |
| CGContextDrawPath(mrShared.maContextHolder.get(), kCGPathStroke); |
| |
| tools::Rectangle aRefreshRect(nX1, nY1, nX2, nY2); |
| (void)aRefreshRect; |
| // Is a call to RefreshRect( aRefreshRect ) missing here? |
| } |
| |
| void AquaGraphicsBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth, |
| tools::Long nHeight) |
| { |
| if (!mrShared.checkContext()) |
| return; |
| |
| CGRect aRect = CGRectMake(nX, nY, nWidth, nHeight); |
| if (mrShared.isPenActive()) |
| { |
| aRect.origin.x += 0.5; |
| aRect.origin.y += 0.5; |
| aRect.size.width -= 1; |
| aRect.size.height -= 1; |
| } |
| |
| if (mrShared.isBrushActive() && mrShared.maFillColor.GetAlpha() == 0) |
| { |
| CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeClear); |
| CGContextClearRect(mrShared.maContextHolder.get(), aRect); |
| CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeNormal); |
| } |
| else |
| { |
| if (mrShared.isBrushActive()) |
| { |
| CGContextFillRect(mrShared.maContextHolder.get(), aRect); |
| } |
| if (mrShared.isPenActive()) |
| { |
| CGContextStrokeRect(mrShared.maContextHolder.get(), aRect); |
| } |
| } |
| mrShared.refreshRect(nX, nY, nWidth, nHeight); |
| } |
| |
| void AquaGraphicsBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPointArray) |
| { |
| if (nPoints < 1) |
| return; |
| |
| if (!mrShared.checkContext()) |
| return; |
| |
| tools::Long nX = 0, nY = 0, nWidth = 0, nHeight = 0; |
| getBoundRect(nPoints, pPointArray, nX, nY, nWidth, nHeight); |
| |
| float fX, fY; |
| CGContextBeginPath(mrShared.maContextHolder.get()); |
| alignLinePoint(pPointArray, fX, fY); |
| CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY); |
| pPointArray++; |
| |
| for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPointArray++) |
| { |
| alignLinePoint(pPointArray, fX, fY); |
| CGContextAddLineToPoint(mrShared.maContextHolder.get(), fX, fY); |
| } |
| CGContextStrokePath(mrShared.maContextHolder.get()); |
| |
| mrShared.refreshRect(nX, nY, nWidth, nHeight); |
| } |
| |
| void AquaGraphicsBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPointArray) |
| { |
| if (nPoints <= 1) |
| return; |
| |
| if (!mrShared.checkContext()) |
| return; |
| |
| tools::Long nX = 0, nY = 0, nWidth = 0, nHeight = 0; |
| getBoundRect(nPoints, pPointArray, nX, nY, nWidth, nHeight); |
| |
| CGPathDrawingMode eMode; |
| if (mrShared.isBrushActive() && mrShared.isPenActive()) |
| { |
| eMode = kCGPathEOFillStroke; |
| } |
| else if (mrShared.isPenActive()) |
| { |
| eMode = kCGPathStroke; |
| } |
| else if (mrShared.isBrushActive()) |
| { |
| eMode = kCGPathEOFill; |
| } |
| else |
| { |
| SAL_WARN("vcl.quartz", "Neither pen nor brush visible"); |
| return; |
| } |
| |
| CGContextBeginPath(mrShared.maContextHolder.get()); |
| |
| if (mrShared.isPenActive()) |
| { |
| float fX, fY; |
| alignLinePoint(pPointArray, fX, fY); |
| CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY); |
| pPointArray++; |
| for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPointArray++) |
| { |
| alignLinePoint(pPointArray, fX, fY); |
| CGContextAddLineToPoint(mrShared.maContextHolder.get(), fX, fY); |
| } |
| } |
| else |
| { |
| CGContextMoveToPoint(mrShared.maContextHolder.get(), pPointArray->getX(), |
| pPointArray->getY()); |
| pPointArray++; |
| for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPointArray++) |
| { |
| CGContextAddLineToPoint(mrShared.maContextHolder.get(), pPointArray->getX(), |
| pPointArray->getY()); |
| } |
| } |
| |
| CGContextClosePath(mrShared.maContextHolder.get()); |
| CGContextDrawPath(mrShared.maContextHolder.get(), eMode); |
| |
| mrShared.refreshRect(nX, nY, nWidth, nHeight); |
| } |
| |
| void AquaGraphicsBackend::drawPolyPolygon(sal_uInt32 nPolyCount, const sal_uInt32* pPoints, |
| const Point** ppPtAry) |
| { |
| if (nPolyCount <= 0) |
| return; |
| |
| if (!mrShared.checkContext()) |
| return; |
| |
| // find bound rect |
| tools::Long leftX = 0, topY = 0, maxWidth = 0, maxHeight = 0; |
| |
| getBoundRect(pPoints[0], ppPtAry[0], leftX, topY, maxWidth, maxHeight); |
| |
| for (sal_uInt32 n = 1; n < nPolyCount; n++) |
| { |
| tools::Long nX = leftX, nY = topY, nW = maxWidth, nH = maxHeight; |
| getBoundRect(pPoints[n], ppPtAry[n], nX, nY, nW, nH); |
| if (nX < leftX) |
| { |
| maxWidth += leftX - nX; |
| leftX = nX; |
| } |
| if (nY < topY) |
| { |
| maxHeight += topY - nY; |
| topY = nY; |
| } |
| if (nX + nW > leftX + maxWidth) |
| { |
| maxWidth = nX + nW - leftX; |
| } |
| if (nY + nH > topY + maxHeight) |
| { |
| maxHeight = nY + nH - topY; |
| } |
| } |
| |
| // prepare drawing mode |
| CGPathDrawingMode eMode; |
| if (mrShared.isBrushActive() && mrShared.isPenActive()) |
| { |
| eMode = kCGPathEOFillStroke; |
| } |
| else if (mrShared.isPenActive()) |
| { |
| eMode = kCGPathStroke; |
| } |
| else if (mrShared.isBrushActive()) |
| { |
| eMode = kCGPathEOFill; |
| } |
| else |
| { |
| SAL_WARN("vcl.quartz", "Neither pen nor brush visible"); |
| return; |
| } |
| |
| // convert to CGPath |
| CGContextBeginPath(mrShared.maContextHolder.get()); |
| if (mrShared.isPenActive()) |
| { |
| for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++) |
| { |
| const sal_uInt32 nPoints = pPoints[nPoly]; |
| if (nPoints > 1) |
| { |
| const Point* pPtAry = ppPtAry[nPoly]; |
| float fX, fY; |
| |
| alignLinePoint(pPtAry, fX, fY); |
| CGContextMoveToPoint(mrShared.maContextHolder.get(), fX, fY); |
| pPtAry++; |
| |
| for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++) |
| { |
| alignLinePoint(pPtAry, fX, fY); |
| CGContextAddLineToPoint(mrShared.maContextHolder.get(), fX, fY); |
| } |
| CGContextClosePath(mrShared.maContextHolder.get()); |
| } |
| } |
| } |
| else |
| { |
| for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++) |
| { |
| const sal_uInt32 nPoints = pPoints[nPoly]; |
| if (nPoints > 1) |
| { |
| const Point* pPtAry = ppPtAry[nPoly]; |
| CGContextMoveToPoint(mrShared.maContextHolder.get(), pPtAry->getX(), |
| pPtAry->getY()); |
| pPtAry++; |
| for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++) |
| { |
| CGContextAddLineToPoint(mrShared.maContextHolder.get(), pPtAry->getX(), |
| pPtAry->getY()); |
| } |
| CGContextClosePath(mrShared.maContextHolder.get()); |
| } |
| } |
| } |
| |
| CGContextDrawPath(mrShared.maContextHolder.get(), eMode); |
| |
| mrShared.refreshRect(leftX, topY, maxWidth, maxHeight); |
| } |
| |
| void AquaGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice, |
| const basegfx::B2DPolyPolygon& rPolyPolygon, |
| double fTransparency) |
| { |
| #ifdef IOS |
| if (!mrShared.maContextHolder.isSet()) |
| return; |
| #endif |
| |
| // short circuit if there is nothing to do |
| if (rPolyPolygon.count() == 0) |
| return; |
| |
| // ignore invisible polygons |
| if ((fTransparency >= 1.0) || (fTransparency < 0)) |
| return; |
| |
| // Fallback: Transform to DeviceCoordinates |
| basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon); |
| aPolyPolygon.transform(rObjectToDevice); |
| |
| // setup poly-polygon path |
| CGMutablePathRef xPath = CGPathCreateMutable(); |
| // tdf#120252 Use the correct, already transformed PolyPolygon (as long as |
| // the transformation is not used here...) |
| for (auto const& rPolygon : std::as_const(aPolyPolygon)) |
| { |
| AddPolygonToPath(xPath, rPolygon, true, !getAntiAlias(), mrShared.isPenActive()); |
| } |
| |
| const CGRect aRefreshRect = CGPathGetBoundingBox(xPath); |
| // #i97317# workaround for Quartz having problems with drawing small polygons |
| if (aRefreshRect.size.width > 0.125 || aRefreshRect.size.height > 0.125) |
| { |
| // prepare drawing mode |
| CGPathDrawingMode eMode; |
| if (mrShared.isBrushActive() && mrShared.isPenActive()) |
| { |
| eMode = kCGPathEOFillStroke; |
| } |
| else if (mrShared.isPenActive()) |
| { |
| eMode = kCGPathStroke; |
| } |
| else if (mrShared.isBrushActive()) |
| { |
| eMode = kCGPathEOFill; |
| } |
| else |
| { |
| SAL_WARN("vcl.quartz", "Neither pen nor brush visible"); |
| CGPathRelease(xPath); |
| return; |
| } |
| |
| // use the path to prepare the graphics context |
| mrShared.maContextHolder.saveState(); |
| CGContextBeginPath(mrShared.maContextHolder.get()); |
| CGContextAddPath(mrShared.maContextHolder.get(), xPath); |
| |
| // draw path with antialiased polygon |
| CGContextSetShouldAntialias(mrShared.maContextHolder.get(), getAntiAlias()); |
| CGContextSetAlpha(mrShared.maContextHolder.get(), 1.0 - fTransparency); |
| CGContextDrawPath(mrShared.maContextHolder.get(), eMode); |
| mrShared.maContextHolder.restoreState(); |
| |
| // mark modified rectangle as updated |
| refreshRect(aRefreshRect); |
| } |
| |
| CGPathRelease(xPath); |
| } |
| |
| bool AquaGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice, |
| const basegfx::B2DPolygon& rPolyLine, double fTransparency, |
| double fLineWidth, |
| const std::vector<double>* pStroke, // MM01 |
| basegfx::B2DLineJoin eLineJoin, |
| css::drawing::LineCap eLineCap, double fMiterMinimumAngle, |
| bool bPixelSnapHairline) |
| { |
| // MM01 check done for simple reasons |
| if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0) |
| { |
| return true; |
| } |
| |
| #ifdef IOS |
| if (!mrShared.checkContext()) |
| return false; |
| #endif |
| |
| // tdf#124848 get correct LineWidth in discrete coordinates, |
| if (fLineWidth == 0) // hairline |
| fLineWidth = 1.0; |
| else // Adjust line width for object-to-device scale. |
| fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength(); |
| |
| // #i101491# Aqua does not support B2DLineJoin::NONE; return false to use |
| // the fallback (own geometry preparation) |
| // #i104886# linejoin-mode and thus the above only applies to "fat" lines |
| if ((basegfx::B2DLineJoin::NONE == eLineJoin) && (fLineWidth > 1.3)) |
| return false; |
| |
| // MM01 need to do line dashing as fallback stuff here now |
| const double fDotDashLength( |
| nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0); |
| const bool bStrokeUsed(0.0 != fDotDashLength); |
| assert(!bStrokeUsed || (bStrokeUsed && pStroke)); |
| basegfx::B2DPolyPolygon aPolyPolygonLine; |
| |
| if (bStrokeUsed) |
| { |
| // apply LineStyle |
| basegfx::utils::applyLineDashing(rPolyLine, // source |
| *pStroke, // pattern |
| &aPolyPolygonLine, // target for lines |
| nullptr, // target for gaps |
| fDotDashLength); // full length if available |
| } |
| else |
| { |
| // no line dashing, just copy |
| aPolyPolygonLine.append(rPolyLine); |
| } |
| |
| // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline |
| aPolyPolygonLine.transform(rObjectToDevice); |
| if (bPixelSnapHairline) |
| { |
| aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine); |
| } |
| |
| // setup line attributes |
| CGLineJoin aCGLineJoin = kCGLineJoinMiter; |
| switch (eLineJoin) |
| { |
| case basegfx::B2DLineJoin::NONE: |
| aCGLineJoin = /*TODO?*/ kCGLineJoinMiter; |
| break; |
| case basegfx::B2DLineJoin::Bevel: |
| aCGLineJoin = kCGLineJoinBevel; |
| break; |
| case basegfx::B2DLineJoin::Miter: |
| aCGLineJoin = kCGLineJoinMiter; |
| break; |
| case basegfx::B2DLineJoin::Round: |
| aCGLineJoin = kCGLineJoinRound; |
| break; |
| } |
| // convert miter minimum angle to miter limit |
| CGFloat fCGMiterLimit = 1.0 / sin(std::max(fMiterMinimumAngle, 0.01 * M_PI) / 2.0); |
| // setup cap attribute |
| CGLineCap aCGLineCap(kCGLineCapButt); |
| |
| switch (eLineCap) |
| { |
| default: // css::drawing::LineCap_BUTT: |
| { |
| aCGLineCap = kCGLineCapButt; |
| break; |
| } |
| case css::drawing::LineCap_ROUND: |
| { |
| aCGLineCap = kCGLineCapRound; |
| break; |
| } |
| case css::drawing::LineCap_SQUARE: |
| { |
| aCGLineCap = kCGLineCapSquare; |
| break; |
| } |
| } |
| |
| // setup poly-polygon path |
| CGMutablePathRef xPath = CGPathCreateMutable(); |
| |
| // MM01 todo - I assume that this is OKAY to be done in one run for quartz |
| // but this NEEDS to be checked/verified |
| for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++) |
| { |
| const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a)); |
| AddPolygonToPath(xPath, aPolyLine, aPolyLine.isClosed(), !getAntiAlias(), true); |
| } |
| |
| const CGRect aRefreshRect = CGPathGetBoundingBox(xPath); |
| // #i97317# workaround for Quartz having problems with drawing small polygons |
| if ((aRefreshRect.size.width > 0.125) || (aRefreshRect.size.height > 0.125)) |
| { |
| // use the path to prepare the graphics context |
| mrShared.maContextHolder.saveState(); |
| CGContextBeginPath(mrShared.maContextHolder.get()); |
| CGContextAddPath(mrShared.maContextHolder.get(), xPath); |
| // draw path with antialiased line |
| CGContextSetShouldAntialias(mrShared.maContextHolder.get(), getAntiAlias()); |
| CGContextSetAlpha(mrShared.maContextHolder.get(), 1.0 - fTransparency); |
| CGContextSetLineJoin(mrShared.maContextHolder.get(), aCGLineJoin); |
| CGContextSetLineCap(mrShared.maContextHolder.get(), aCGLineCap); |
| CGContextSetLineWidth(mrShared.maContextHolder.get(), fLineWidth); |
| CGContextSetMiterLimit(mrShared.maContextHolder.get(), fCGMiterLimit); |
| CGContextDrawPath(mrShared.maContextHolder.get(), kCGPathStroke); |
| mrShared.maContextHolder.restoreState(); |
| |
| // mark modified rectangle as updated |
| refreshRect(aRefreshRect); |
| } |
| |
| CGPathRelease(xPath); |
| |
| return true; |
| } |
| |
| bool AquaGraphicsBackend::drawPolyLineBezier(sal_uInt32 /*nPoints*/, const Point* /*pPointArray*/, |
| const PolyFlags* /*pFlagArray*/) |
| { |
| return false; |
| } |
| |
| bool AquaGraphicsBackend::drawPolygonBezier(sal_uInt32 /*nPoints*/, const Point* /*pPointArray*/, |
| const PolyFlags* /*pFlagArray*/) |
| { |
| return false; |
| } |
| |
| bool AquaGraphicsBackend::drawPolyPolygonBezier(sal_uInt32 /*nPoly*/, const sal_uInt32* /*pPoints*/, |
| const Point* const* /*pPointArray*/, |
| const PolyFlags* const* /*pFlagArray*/) |
| { |
| return false; |
| } |
| |
| void AquaGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) |
| { |
| if (!mrShared.checkContext()) |
| return; |
| |
| CGImageRef xImage = rSalBitmap.CreateCroppedImage( |
| static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), |
| static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight)); |
| if (!xImage) |
| return; |
| |
| const CGRect aDstRect |
| = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight); |
| CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xImage); |
| |
| CGImageRelease(xImage); |
| refreshRect(aDstRect); |
| } |
| |
| void AquaGraphicsBackend::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap, |
| Color nMaskColor) |
| { |
| if (!mrShared.checkContext()) |
| return; |
| |
| CGImageRef xImage = rSalBitmap.CreateColorMask( |
| rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, nMaskColor); |
| if (!xImage) |
| return; |
| |
| const CGRect aDstRect |
| = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight); |
| CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xImage); |
| CGImageRelease(xImage); |
| refreshRect(aDstRect); |
| } |
| |
| std::shared_ptr<SalBitmap> AquaGraphicsBackend::getBitmap(tools::Long nX, tools::Long nY, |
| tools::Long nDX, tools::Long nDY, |
| bool /*bWithoutAlpha*/) |
| { |
| SAL_WARN_IF(!mrShared.maLayer.isSet(), "vcl.quartz", |
| "AquaSalGraphics::getBitmap() with no layer this=" << this); |
| |
| mrShared.applyXorContext(); |
| |
| std::shared_ptr<QuartzSalBitmap> pBitmap = std::make_shared<QuartzSalBitmap>(); |
| if (!pBitmap->Create(mrShared.maLayer, mrShared.mnBitmapDepth, nX, nY, nDX, nDY, |
| mrShared.isFlipped())) |
| { |
| pBitmap = nullptr; |
| } |
| return pBitmap; |
| } |
| |
| Color AquaGraphicsBackend::getPixel(tools::Long nX, tools::Long nY) |
| { |
| // return default value on printers or when out of bounds |
| if (!mrShared.maLayer.isSet() || (nX < 0) || (nX >= mrShared.mnWidth) || (nY < 0) |
| || (nY >= mrShared.mnHeight)) |
| { |
| return COL_BLACK; |
| } |
| |
| // prepare creation of matching a CGBitmapContext |
| #if defined OSL_BIGENDIAN |
| struct |
| { |
| unsigned char b, g, r, a; |
| } aPixel; |
| #else |
| struct |
| { |
| unsigned char a, r, g, b; |
| } aPixel; |
| #endif |
| |
| // create a one-pixel bitmap context |
| // TODO: is it worth to cache it? |
| CGContextRef xOnePixelContext = CGBitmapContextCreate( |
| &aPixel, 1, 1, 8, 32, GetSalData()->mxRGBSpace, |
| uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Big)); |
| |
| // update this graphics layer |
| mrShared.applyXorContext(); |
| |
| // copy the requested pixel into the bitmap context |
| if (mrShared.isFlipped()) |
| { |
| nY = mrShared.mnHeight - nY; |
| } |
| const CGPoint aCGPoint = CGPointMake(-nX, -nY); |
| CGContextDrawLayerAtPoint(xOnePixelContext, aCGPoint, mrShared.maLayer.get()); |
| |
| CGContextRelease(xOnePixelContext); |
| |
| Color nColor(ColorAlpha, aPixel.a, aPixel.r, aPixel.g, aPixel.b); |
| return nColor; |
| } |
| |
| void AquaSalGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY) |
| { |
| #ifndef IOS |
| if (!mnRealDPIY) |
| { |
| initResolution((maShared.mbWindow && maShared.mpFrame) ? maShared.mpFrame->getNSWindow() |
| : nil); |
| } |
| |
| if (maShared.mbPrinter) |
| { |
| rDPIX = rDPIY = 720; |
| } |
| else |
| { |
| rDPIX = mnRealDPIX; |
| rDPIY = mnRealDPIY; |
| } |
| #else |
| // This *must* be 96 or else the iOS app will behave very badly (tiles are scaled wrongly and |
| // don't match each others at their boundaries, and other issues). But *why* it must be 96 I |
| // have no idea. The commit that changed it to 96 from (the arbitrary) 200 did not say. If you |
| // know where else 96 is explicitly or implicitly hard-coded, please modify this comment. |
| |
| // Follow-up: It might be this: in 'online', loleaflet/src/map/Map.js: |
| // 15 = 1440 twips-per-inch / 96 dpi. |
| // Chosen to match previous hardcoded value of 3840 for |
| // the current tile pixel size of 256. |
| rDPIX = rDPIY = 96; |
| #endif |
| } |
| |
| void AquaGraphicsBackend::pattern50Fill() |
| { |
| static const CGFloat aFillCol[4] = { 1, 1, 1, 1 }; |
| static const CGPatternCallbacks aCallback = { 0, &drawPattern50, nullptr }; |
| static const CGColorSpaceRef mxP50Space = CGColorSpaceCreatePattern(GetSalData()->mxRGBSpace); |
| static const CGPatternRef mxP50Pattern |
| = CGPatternCreate(nullptr, CGRectMake(0, 0, 4, 4), CGAffineTransformIdentity, 4, 4, |
| kCGPatternTilingConstantSpacing, false, &aCallback); |
| SAL_WARN_IF(!mrShared.maContextHolder.get(), "vcl.quartz", "maContextHolder.get() is NULL"); |
| CGContextSetFillColorSpace(mrShared.maContextHolder.get(), mxP50Space); |
| CGContextSetFillPattern(mrShared.maContextHolder.get(), mxP50Pattern, aFillCol); |
| CGContextFillPath(mrShared.maContextHolder.get()); |
| } |
| |
| void AquaGraphicsBackend::invert(tools::Long nX, tools::Long nY, tools::Long nWidth, |
| tools::Long nHeight, SalInvert nFlags) |
| { |
| if (mrShared.checkContext()) |
| { |
| CGRect aCGRect = CGRectMake(nX, nY, nWidth, nHeight); |
| mrShared.maContextHolder.saveState(); |
| if (nFlags & SalInvert::TrackFrame) |
| { |
| const CGFloat dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line |
| CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference); |
| CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0); |
| CGContextSetLineDash(mrShared.maContextHolder.get(), 0, dashLengths, 2); |
| CGContextSetLineWidth(mrShared.maContextHolder.get(), 2.0); |
| CGContextStrokeRect(mrShared.maContextHolder.get(), aCGRect); |
| } |
| else if (nFlags & SalInvert::N50) |
| { |
| //CGContextSetAllowsAntialiasing( maContextHolder.get(), false ); |
| CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference); |
| CGContextAddRect(mrShared.maContextHolder.get(), aCGRect); |
| pattern50Fill(); |
| } |
| else // just invert |
| { |
| CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference); |
| CGContextSetRGBFillColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0); |
| CGContextFillRect(mrShared.maContextHolder.get(), aCGRect); |
| } |
| mrShared.maContextHolder.restoreState(); |
| refreshRect(aCGRect); |
| } |
| } |
| |
| namespace |
| { |
| CGPoint* makeCGptArray(sal_uInt32 nPoints, const Point* pPtAry) |
| { |
| CGPoint* CGpoints = new CGPoint[nPoints]; |
| for (sal_uLong i = 0; i < nPoints; i++) |
| { |
| CGpoints[i].x = pPtAry[i].getX(); |
| CGpoints[i].y = pPtAry[i].getY(); |
| } |
| return CGpoints; |
| } |
| |
| } // end anonymous ns |
| |
| void AquaGraphicsBackend::invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags) |
| { |
| if (mrShared.checkContext()) |
| { |
| mrShared.maContextHolder.saveState(); |
| CGPoint* CGpoints = makeCGptArray(nPoints, pPtAry); |
| CGContextAddLines(mrShared.maContextHolder.get(), CGpoints, nPoints); |
| if (nSalFlags & SalInvert::TrackFrame) |
| { |
| const CGFloat dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line |
| CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference); |
| CGContextSetRGBStrokeColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0); |
| CGContextSetLineDash(mrShared.maContextHolder.get(), 0, dashLengths, 2); |
| CGContextSetLineWidth(mrShared.maContextHolder.get(), 2.0); |
| CGContextStrokePath(mrShared.maContextHolder.get()); |
| } |
| else if (nSalFlags & SalInvert::N50) |
| { |
| CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference); |
| pattern50Fill(); |
| } |
| else // just invert |
| { |
| CGContextSetBlendMode(mrShared.maContextHolder.get(), kCGBlendModeDifference); |
| CGContextSetRGBFillColor(mrShared.maContextHolder.get(), 1.0, 1.0, 1.0, 1.0); |
| CGContextFillPath(mrShared.maContextHolder.get()); |
| } |
| const CGRect aRefreshRect = CGContextGetClipBoundingBox(mrShared.maContextHolder.get()); |
| mrShared.maContextHolder.restoreState(); |
| delete[] CGpoints; |
| refreshRect(aRefreshRect); |
| } |
| } |
| |
| #ifndef IOS |
| bool AquaGraphicsBackend::drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth, |
| tools::Long nHeight, void* pEpsData, sal_uInt32 nByteCount) |
| { |
| // convert the raw data to an NSImageRef |
| NSData* xNSData = [NSData dataWithBytes:pEpsData length:static_cast<int>(nByteCount)]; |
| SAL_WNODEPRECATED_DECLARATIONS_PUSH |
| // 'NSEPSImageRep' is deprecated: first deprecated in macOS 14.0 - `NSEPSImageRep` instances |
| // cannot be created on macOS 14.0 and later |
| NSImageRep* xEpsImage = [NSEPSImageRep imageRepWithData:xNSData]; |
| SAL_WNODEPRECATED_DECLARATIONS_POP |
| if (!xEpsImage) |
| { |
| return false; |
| } |
| // get the target context |
| if (!mrShared.checkContext()) |
| { |
| return false; |
| } |
| // NOTE: flip drawing, else the nsimage would be drawn upside down |
| mrShared.maContextHolder.saveState(); |
| // CGContextTranslateCTM( maContextHolder.get(), 0, +mnHeight ); |
| CGContextScaleCTM(mrShared.maContextHolder.get(), +1, -1); |
| nY = /*mnHeight*/ -(nY + nHeight); |
| |
| // prepare the target context |
| NSGraphicsContext* pOrigNSCtx = [NSGraphicsContext currentContext]; |
| [pOrigNSCtx retain]; |
| |
| // create new context |
| NSGraphicsContext* pDrawNSCtx = |
| [NSGraphicsContext graphicsContextWithCGContext:mrShared.maContextHolder.get() |
| flipped:mrShared.isFlipped()]; |
| // set it, setCurrentContext also releases the previously set one |
| [NSGraphicsContext setCurrentContext:pDrawNSCtx]; |
| |
| // draw the EPS |
| const NSRect aDstRect = NSMakeRect(nX, nY, nWidth, nHeight); |
| const bool bOK = [xEpsImage drawInRect:aDstRect]; |
| |
| // restore the NSGraphicsContext |
| [NSGraphicsContext setCurrentContext:pOrigNSCtx]; |
| [pOrigNSCtx release]; // restore the original retain count |
| |
| mrShared.maContextHolder.restoreState(); |
| // mark the destination rectangle as updated |
| refreshRect(aDstRect); |
| |
| return bOK; |
| } |
| #else |
| bool AquaGraphicsBackend::drawEPS(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/, |
| tools::Long /*nHeight*/, void* /*pEpsData*/, |
| sal_uInt32 /*nByteCount*/) |
| { |
| return false; |
| } |
| #endif |
| |
| void AquaGraphicsBackend::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) |
| { |
| if (!mrShared.checkContext()) |
| { |
| assert(false); |
| return; |
| } |
| |
| CGImageRef xImage = rSalBitmap.CreateCroppedImage( |
| static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), |
| static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight)); |
| assert(xImage); |
| if (!xImage) |
| return; |
| |
| const CGRect aDstRect |
| = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight); |
| CGContextDrawImage(mrShared.maContextHolder.get(), aDstRect, xImage); |
| |
| CGImageRelease(xImage); |
| refreshRect(aDstRect); |
| } |
| |
| bool AquaGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint& rNull, |
| const basegfx::B2DPoint& rX, |
| const basegfx::B2DPoint& rY, |
| const SalBitmap& rSrcBitmap, double fAlpha) |
| { |
| if (!mrShared.checkContext()) |
| return true; |
| |
| if (fAlpha != 1.0) |
| return false; |
| |
| // get the Quartz image |
| const Size aSize = rSrcBitmap.GetSize(); |
| CGImageRef xImage |
| = rSrcBitmap.CreateCroppedImage(0, 0, int(aSize.Width()), int(aSize.Height())); |
| |
| if (!xImage) |
| return false; |
| |
| // setup the image transformation |
| // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points |
| mrShared.maContextHolder.saveState(); |
| const basegfx::B2DVector aXRel = rX - rNull; |
| const basegfx::B2DVector aYRel = rY - rNull; |
| const CGAffineTransform aCGMat = CGAffineTransformMake( |
| aXRel.getX() / aSize.Width(), aXRel.getY() / aSize.Width(), aYRel.getX() / aSize.Height(), |
| aYRel.getY() / aSize.Height(), rNull.getX(), rNull.getY()); |
| |
| CGContextConcatCTM(mrShared.maContextHolder.get(), aCGMat); |
| |
| // draw the transformed image |
| const CGRect aSrcRect = CGRectMake(0, 0, aSize.Width(), aSize.Height()); |
| CGContextDrawImage(mrShared.maContextHolder.get(), aSrcRect, xImage); |
| |
| CGImageRelease(xImage); |
| |
| // restore the Quartz graphics state |
| mrShared.maContextHolder.restoreState(); |
| |
| // mark the destination as painted |
| const CGRect aDstRect = CGRectApplyAffineTransform(aSrcRect, aCGMat); |
| refreshRect(aDstRect); |
| |
| return true; |
| } |
| |
| bool AquaGraphicsBackend::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth, |
| tools::Long nHeight, sal_uInt8 nTransparency) |
| { |
| if (!mrShared.checkContext()) |
| return true; |
| |
| // save the current state |
| mrShared.maContextHolder.saveState(); |
| CGContextSetAlpha(mrShared.maContextHolder.get(), (100 - nTransparency) * (1.0 / 100)); |
| |
| CGRect aRect = CGRectMake(nX, nY, nWidth - 1, nHeight - 1); |
| if (mrShared.isPenActive()) |
| { |
| aRect.origin.x += 0.5; |
| aRect.origin.y += 0.5; |
| } |
| |
| CGContextBeginPath(mrShared.maContextHolder.get()); |
| CGContextAddRect(mrShared.maContextHolder.get(), aRect); |
| CGContextDrawPath(mrShared.maContextHolder.get(), kCGPathFill); |
| |
| mrShared.maContextHolder.restoreState(); |
| refreshRect(aRect); |
| |
| return true; |
| } |
| |
| bool AquaGraphicsBackend::drawGradient(const tools::PolyPolygon& /*rPolygon*/, |
| const Gradient& /*rGradient*/) |
| { |
| return false; |
| } |
| |
| bool AquaGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon const& /*rPolyPolygon*/, |
| SalGradient const& /*rGradient*/) |
| { |
| return false; |
| } |
| |
| bool AquaGraphicsBackend::supportsOperation(OutDevSupportType /*eType*/) const { return false; } |
| |
| /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |