Files
libreoffice/vcl/qt5/Qt5Graphics_GDI.cxx

675 lines
22 KiB
C++
Raw Normal View History

/* -*- 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 .
*/
#include <Qt5Graphics.hxx>
#include <Qt5Bitmap.hxx>
#include <Qt5Painter.hxx>
#include <sal/log.hxx>
#include <QtGui/QPainter>
#include <QtGui/QScreen>
#include <QtGui/QWindow>
#include <QtWidgets/QWidget>
#include <basegfx/polygon/b2dpolygontools.hxx>
static const basegfx::B2DPoint aHalfPointOfs(0.5, 0.5);
static void AddPolygonToPath(QPainterPath& rPath, const basegfx::B2DPolygon& rPolygon,
bool bClosePath, bool bPixelSnap, bool bLineDraw)
{
const int nPointCount = rPolygon.count();
// short circuit if there is nothing to do
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
rPath.moveTo(aPoint.getX(), aPoint.getY());
continue;
}
bool bPendingCurve = false;
if (bHasCurves)
{
bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
}
if (!bPendingCurve) // line segment
rPath.lineTo(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;
}
rPath.cubicTo(aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(),
aPoint.getY());
}
}
if (bClosePath)
rPath.closeSubpath();
}
static bool AddPolyPolygonToPath(QPainterPath& rPath, const basegfx::B2DPolyPolygon& rPolyPoly,
bool bPixelSnap, bool bLineDraw)
{
if (rPolyPoly.count() == 0)
return false;
for (auto const& rPolygon : rPolyPoly)
{
AddPolygonToPath(rPath, rPolygon, true, bPixelSnap, bLineDraw);
}
return true;
}
bool Qt5Graphics::setClipRegion(const vcl::Region& rRegion)
{
if (rRegion.IsRectangle())
{
m_aClipRegion = toQRect(rRegion.GetBoundRect());
if (!m_aClipPath.isEmpty())
{
QPainterPath aPath;
m_aClipPath.swap(aPath);
}
}
else if (!rRegion.HasPolyPolygonOrB2DPolyPolygon())
{
QRegion aQRegion;
RectangleVector aRectangles;
rRegion.GetRegionRectangles(aRectangles);
for (auto& rRect : aRectangles)
aQRegion += toQRect(rRect);
m_aClipRegion = aQRegion;
if (!m_aClipPath.isEmpty())
{
QPainterPath aPath;
m_aClipPath.swap(aPath);
}
}
else
{
QPainterPath aPath;
const basegfx::B2DPolyPolygon aPolyClip(rRegion.GetAsB2DPolyPolygon());
AddPolyPolygonToPath(aPath, aPolyClip, !getAntiAliasB2DDraw(), false);
m_aClipPath.swap(aPath);
if (!m_aClipRegion.isEmpty())
{
QRegion aRegion;
m_aClipRegion.swap(aRegion);
}
}
return true;
}
void Qt5Graphics::ResetClipRegion()
{
if (m_pQImage)
m_aClipRegion = QRegion(m_pQImage->rect());
else
m_aClipRegion = QRegion();
if (!m_aClipPath.isEmpty())
{
QPainterPath aPath;
m_aClipPath.swap(aPath);
}
}
void Qt5Graphics::drawPixel(long nX, long nY)
{
Qt5Painter aPainter(*this);
aPainter.drawPoint(nX, nY);
aPainter.update(nX, nY, 1, 1);
}
void Qt5Graphics::drawPixel(long nX, long nY, Color nColor)
{
Qt5Painter aPainter(*this);
aPainter.setPen(toQColor(nColor));
aPainter.setPen(Qt::SolidLine);
aPainter.drawPoint(nX, nY);
aPainter.update(nX, nY, 1, 1);
}
void Qt5Graphics::drawLine(long nX1, long nY1, long nX2, long nY2)
{
Qt5Painter aPainter(*this);
aPainter.drawLine(nX1, nY1, nX2, nY2);
long tmp;
if (nX1 > nX2)
{
tmp = nX1;
nX1 = nX2;
nX2 = tmp;
}
if (nY1 > nY2)
{
tmp = nY1;
nY1 = nY2;
nY2 = tmp;
}
aPainter.update(nX1, nY1, nX2 - nX1 + 1, nY2 - nY1 + 1);
}
void Qt5Graphics::drawRect(long nX, long nY, long nWidth, long nHeight)
{
if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
return;
Qt5Painter aPainter(*this, true);
if (SALCOLOR_NONE != m_aFillColor)
aPainter.fillRect(nX, nY, nWidth, nHeight, aPainter.brush());
if (SALCOLOR_NONE != m_aLineColor)
aPainter.drawRect(nX, nY, nWidth, nHeight);
aPainter.update(nX, nY, nWidth, nHeight);
}
void Qt5Graphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry)
{
if (0 == nPoints)
return;
Qt5Painter aPainter(*this);
QPoint* pPoints = new QPoint[nPoints];
QPoint aTopLeft(pPtAry->mnX, pPtAry->mnY);
QPoint aBottomRight = aTopLeft;
for (sal_uInt32 i = 0; i < nPoints; ++i, ++pPtAry)
{
pPoints[i] = QPoint(pPtAry->mnX, pPtAry->mnY);
if (pPtAry->mnX < aTopLeft.x())
aTopLeft.setX(pPtAry->mnX);
if (pPtAry->mnY < aTopLeft.y())
aTopLeft.setY(pPtAry->mnY);
if (pPtAry->mnX > aBottomRight.x())
aBottomRight.setX(pPtAry->mnX);
if (pPtAry->mnY > aBottomRight.y())
aBottomRight.setY(pPtAry->mnY);
}
aPainter.drawPolyline(pPoints, nPoints);
delete[] pPoints;
aPainter.update(QRect(aTopLeft, aBottomRight));
}
void Qt5Graphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
{
Qt5Painter aPainter(*this, true);
QPolygon aPolygon(nPoints);
for (sal_uInt32 i = 0; i < nPoints; ++i, ++pPtAry)
aPolygon.setPoint(i, pPtAry->mnX, pPtAry->mnY);
aPainter.drawPolygon(aPolygon);
aPainter.update(aPolygon.boundingRect());
}
void Qt5Graphics::drawPolyPolygon(sal_uInt32 nPolyCount, const sal_uInt32* pPoints,
PCONSTSALPOINT* ppPtAry)
{
// ignore invisible polygons
if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
return;
QPainterPath aPath;
for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++)
{
const sal_uInt32 nPoints = pPoints[nPoly];
if (nPoints > 1)
{
const SalPoint* pPtAry = ppPtAry[nPoly];
aPath.moveTo(pPtAry->mnX, pPtAry->mnY);
pPtAry++;
for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++)
aPath.lineTo(pPtAry->mnX, pPtAry->mnY);
aPath.closeSubpath();
}
}
Qt5Painter aPainter(*this, true);
aPainter.drawPath(aPath);
aPainter.update(aPath.boundingRect());
}
bool Qt5Graphics::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
const basegfx::B2DPolyPolygon& rPolyPolygon, double fTransparency)
{
// ignore invisible polygons
if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
return true;
if ((fTransparency >= 1.0) || (fTransparency < 0))
return true;
// Fallback: Transform to DeviceCoordinates
basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
aPolyPolygon.transform(rObjectToDevice);
QPainterPath aPath;
// ignore empty polygons
if (!AddPolyPolygonToPath(aPath, aPolyPolygon, !getAntiAliasB2DDraw(),
m_aLineColor != SALCOLOR_NONE))
return true;
Qt5Painter aPainter(*this, true, 255 * (1.0 - fTransparency));
aPainter.drawPath(aPath);
aPainter.update(aPath.boundingRect());
return true;
}
bool Qt5Graphics::drawPolyLineBezier(sal_uInt32 /*nPoints*/, const SalPoint* /*pPtAry*/,
const PolyFlags* /*pFlgAry*/)
{
return false;
}
bool Qt5Graphics::drawPolygonBezier(sal_uInt32 /*nPoints*/, const SalPoint* /*pPtAry*/,
const PolyFlags* /*pFlgAry*/)
{
return false;
}
bool Qt5Graphics::drawPolyPolygonBezier(sal_uInt32 /*nPoly*/, const sal_uInt32* /*pPoints*/,
const SalPoint* const* /*pPtAry*/,
const PolyFlags* const* /*pFlgAry*/)
{
return false;
}
Support buffering SystemDependent GraphicData This is a first step to allow buffering of system dependent data, especially (but not only) for the system-dependent implementations of graphic output. For example, for B2DPolygon and Win output, it allows buffering the Gdiplus::GraphicsPath instead of re- creating it all the time. To support that, the change includes forwarding the current transformation to the renderers in SalGraphics. The current state in VCL is to transform all and everything to device coordinates at every single paint. I have currently started to do this for ::drawPolyLine implementations. The fallbacks for all systems will at the start of that method just transform the data to device coordinates, so all works as before. This may also be done for FilledPolygon paint in a later step, but most urgent is FatLine painting. An arrangement of shared_ptr/weak_ptr is used so that either the instance buffering (in the example B2DPolygon) or the instance managing it can delete it. The instance managing it currently uses a 1s Timer and a cycle-lifetime management, but that can be extended in the future to e.g. include size hints, too. The mechanism it designed to support multiple Data per buffering element, e.g. for B2DPolygon at the same time system-dependent instances of Gdiplus and Cairo can be buffered, but also PDF-data. This is achieved semi-automatic by using typeid(class).hash_code() as key for organization. The mechanism will be used for now at B2DPolygon, but is not limited to. There is already a similar but less general buffer (see GdiPlusBuffer) that can and will be converted to use this new mechanism. Added vcl/headless Cairo renderer to support given ObjectToDevice transformation (not to transform given B2DPolygon) Added support for CairoPath buffered at B2DPolygon, seems to work well. Need to do more tests Moved usage to templates suggested by Noel Grandin (Noel Grandin <noelgrandin@gmail.com>), thanks for these suggestions. Adapted Win usage to that, too. Converted Win-specific GdiPlus BitmapBuffer to new mechanism, works well. Checked, the manager holds now a mix of bitmap and path data under Win Added a cleanup mechanism to flush all buffered data at DeInitVCL() using flushAll() at SystemDependentDataBuffer Adapted Linux-versions of ::drawPolyLine to support PixelSnapHairline, for now in a simplified version that still allows buffering. This will also be used (and use buffering) for the Cairo-fallback in X11SalGraphics Change-Id: I88d7e438a20b96ddab7707050893bdd590c098c7 Reviewed-on: https://gerrit.libreoffice.org/59555 Tested-by: Armin Le Grand <Armin.Le.Grand@cib.de> Reviewed-by: Armin Le Grand <Armin.Le.Grand@cib.de>
2018-08-24 13:01:08 +02:00
bool Qt5Graphics::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
const basegfx::B2DPolygon& rPolyLine, double fTransparency,
const basegfx::B2DVector& rLineWidths,
basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap,
Support buffering SystemDependent GraphicData This is a first step to allow buffering of system dependent data, especially (but not only) for the system-dependent implementations of graphic output. For example, for B2DPolygon and Win output, it allows buffering the Gdiplus::GraphicsPath instead of re- creating it all the time. To support that, the change includes forwarding the current transformation to the renderers in SalGraphics. The current state in VCL is to transform all and everything to device coordinates at every single paint. I have currently started to do this for ::drawPolyLine implementations. The fallbacks for all systems will at the start of that method just transform the data to device coordinates, so all works as before. This may also be done for FilledPolygon paint in a later step, but most urgent is FatLine painting. An arrangement of shared_ptr/weak_ptr is used so that either the instance buffering (in the example B2DPolygon) or the instance managing it can delete it. The instance managing it currently uses a 1s Timer and a cycle-lifetime management, but that can be extended in the future to e.g. include size hints, too. The mechanism it designed to support multiple Data per buffering element, e.g. for B2DPolygon at the same time system-dependent instances of Gdiplus and Cairo can be buffered, but also PDF-data. This is achieved semi-automatic by using typeid(class).hash_code() as key for organization. The mechanism will be used for now at B2DPolygon, but is not limited to. There is already a similar but less general buffer (see GdiPlusBuffer) that can and will be converted to use this new mechanism. Added vcl/headless Cairo renderer to support given ObjectToDevice transformation (not to transform given B2DPolygon) Added support for CairoPath buffered at B2DPolygon, seems to work well. Need to do more tests Moved usage to templates suggested by Noel Grandin (Noel Grandin <noelgrandin@gmail.com>), thanks for these suggestions. Adapted Win usage to that, too. Converted Win-specific GdiPlus BitmapBuffer to new mechanism, works well. Checked, the manager holds now a mix of bitmap and path data under Win Added a cleanup mechanism to flush all buffered data at DeInitVCL() using flushAll() at SystemDependentDataBuffer Adapted Linux-versions of ::drawPolyLine to support PixelSnapHairline, for now in a simplified version that still allows buffering. This will also be used (and use buffering) for the Cairo-fallback in X11SalGraphics Change-Id: I88d7e438a20b96ddab7707050893bdd590c098c7 Reviewed-on: https://gerrit.libreoffice.org/59555 Tested-by: Armin Le Grand <Armin.Le.Grand@cib.de> Reviewed-by: Armin Le Grand <Armin.Le.Grand@cib.de>
2018-08-24 13:01:08 +02:00
double fMiterMinimumAngle, bool bPixelSnapHairline)
{
if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
return true;
// short circuit if there is nothing to do
Support buffering SystemDependent GraphicData This is a first step to allow buffering of system dependent data, especially (but not only) for the system-dependent implementations of graphic output. For example, for B2DPolygon and Win output, it allows buffering the Gdiplus::GraphicsPath instead of re- creating it all the time. To support that, the change includes forwarding the current transformation to the renderers in SalGraphics. The current state in VCL is to transform all and everything to device coordinates at every single paint. I have currently started to do this for ::drawPolyLine implementations. The fallbacks for all systems will at the start of that method just transform the data to device coordinates, so all works as before. This may also be done for FilledPolygon paint in a later step, but most urgent is FatLine painting. An arrangement of shared_ptr/weak_ptr is used so that either the instance buffering (in the example B2DPolygon) or the instance managing it can delete it. The instance managing it currently uses a 1s Timer and a cycle-lifetime management, but that can be extended in the future to e.g. include size hints, too. The mechanism it designed to support multiple Data per buffering element, e.g. for B2DPolygon at the same time system-dependent instances of Gdiplus and Cairo can be buffered, but also PDF-data. This is achieved semi-automatic by using typeid(class).hash_code() as key for organization. The mechanism will be used for now at B2DPolygon, but is not limited to. There is already a similar but less general buffer (see GdiPlusBuffer) that can and will be converted to use this new mechanism. Added vcl/headless Cairo renderer to support given ObjectToDevice transformation (not to transform given B2DPolygon) Added support for CairoPath buffered at B2DPolygon, seems to work well. Need to do more tests Moved usage to templates suggested by Noel Grandin (Noel Grandin <noelgrandin@gmail.com>), thanks for these suggestions. Adapted Win usage to that, too. Converted Win-specific GdiPlus BitmapBuffer to new mechanism, works well. Checked, the manager holds now a mix of bitmap and path data under Win Added a cleanup mechanism to flush all buffered data at DeInitVCL() using flushAll() at SystemDependentDataBuffer Adapted Linux-versions of ::drawPolyLine to support PixelSnapHairline, for now in a simplified version that still allows buffering. This will also be used (and use buffering) for the Cairo-fallback in X11SalGraphics Change-Id: I88d7e438a20b96ddab7707050893bdd590c098c7 Reviewed-on: https://gerrit.libreoffice.org/59555 Tested-by: Armin Le Grand <Armin.Le.Grand@cib.de> Reviewed-by: Armin Le Grand <Armin.Le.Grand@cib.de>
2018-08-24 13:01:08 +02:00
if (0 == rPolyLine.count())
{
return true;
Support buffering SystemDependent GraphicData This is a first step to allow buffering of system dependent data, especially (but not only) for the system-dependent implementations of graphic output. For example, for B2DPolygon and Win output, it allows buffering the Gdiplus::GraphicsPath instead of re- creating it all the time. To support that, the change includes forwarding the current transformation to the renderers in SalGraphics. The current state in VCL is to transform all and everything to device coordinates at every single paint. I have currently started to do this for ::drawPolyLine implementations. The fallbacks for all systems will at the start of that method just transform the data to device coordinates, so all works as before. This may also be done for FilledPolygon paint in a later step, but most urgent is FatLine painting. An arrangement of shared_ptr/weak_ptr is used so that either the instance buffering (in the example B2DPolygon) or the instance managing it can delete it. The instance managing it currently uses a 1s Timer and a cycle-lifetime management, but that can be extended in the future to e.g. include size hints, too. The mechanism it designed to support multiple Data per buffering element, e.g. for B2DPolygon at the same time system-dependent instances of Gdiplus and Cairo can be buffered, but also PDF-data. This is achieved semi-automatic by using typeid(class).hash_code() as key for organization. The mechanism will be used for now at B2DPolygon, but is not limited to. There is already a similar but less general buffer (see GdiPlusBuffer) that can and will be converted to use this new mechanism. Added vcl/headless Cairo renderer to support given ObjectToDevice transformation (not to transform given B2DPolygon) Added support for CairoPath buffered at B2DPolygon, seems to work well. Need to do more tests Moved usage to templates suggested by Noel Grandin (Noel Grandin <noelgrandin@gmail.com>), thanks for these suggestions. Adapted Win usage to that, too. Converted Win-specific GdiPlus BitmapBuffer to new mechanism, works well. Checked, the manager holds now a mix of bitmap and path data under Win Added a cleanup mechanism to flush all buffered data at DeInitVCL() using flushAll() at SystemDependentDataBuffer Adapted Linux-versions of ::drawPolyLine to support PixelSnapHairline, for now in a simplified version that still allows buffering. This will also be used (and use buffering) for the Cairo-fallback in X11SalGraphics Change-Id: I88d7e438a20b96ddab7707050893bdd590c098c7 Reviewed-on: https://gerrit.libreoffice.org/59555 Tested-by: Armin Le Grand <Armin.Le.Grand@cib.de> Reviewed-by: Armin Le Grand <Armin.Le.Grand@cib.de>
2018-08-24 13:01:08 +02:00
}
// Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
basegfx::B2DPolygon aPolyLine(rPolyLine);
aPolyLine.transform(rObjectToDevice);
if (bPixelSnapHairline)
{
aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);
}
const basegfx::B2DVector aLineWidths(rObjectToDevice * rLineWidths);
// setup poly-polygon path
QPainterPath aPath;
Support buffering SystemDependent GraphicData This is a first step to allow buffering of system dependent data, especially (but not only) for the system-dependent implementations of graphic output. For example, for B2DPolygon and Win output, it allows buffering the Gdiplus::GraphicsPath instead of re- creating it all the time. To support that, the change includes forwarding the current transformation to the renderers in SalGraphics. The current state in VCL is to transform all and everything to device coordinates at every single paint. I have currently started to do this for ::drawPolyLine implementations. The fallbacks for all systems will at the start of that method just transform the data to device coordinates, so all works as before. This may also be done for FilledPolygon paint in a later step, but most urgent is FatLine painting. An arrangement of shared_ptr/weak_ptr is used so that either the instance buffering (in the example B2DPolygon) or the instance managing it can delete it. The instance managing it currently uses a 1s Timer and a cycle-lifetime management, but that can be extended in the future to e.g. include size hints, too. The mechanism it designed to support multiple Data per buffering element, e.g. for B2DPolygon at the same time system-dependent instances of Gdiplus and Cairo can be buffered, but also PDF-data. This is achieved semi-automatic by using typeid(class).hash_code() as key for organization. The mechanism will be used for now at B2DPolygon, but is not limited to. There is already a similar but less general buffer (see GdiPlusBuffer) that can and will be converted to use this new mechanism. Added vcl/headless Cairo renderer to support given ObjectToDevice transformation (not to transform given B2DPolygon) Added support for CairoPath buffered at B2DPolygon, seems to work well. Need to do more tests Moved usage to templates suggested by Noel Grandin (Noel Grandin <noelgrandin@gmail.com>), thanks for these suggestions. Adapted Win usage to that, too. Converted Win-specific GdiPlus BitmapBuffer to new mechanism, works well. Checked, the manager holds now a mix of bitmap and path data under Win Added a cleanup mechanism to flush all buffered data at DeInitVCL() using flushAll() at SystemDependentDataBuffer Adapted Linux-versions of ::drawPolyLine to support PixelSnapHairline, for now in a simplified version that still allows buffering. This will also be used (and use buffering) for the Cairo-fallback in X11SalGraphics Change-Id: I88d7e438a20b96ddab7707050893bdd590c098c7 Reviewed-on: https://gerrit.libreoffice.org/59555 Tested-by: Armin Le Grand <Armin.Le.Grand@cib.de> Reviewed-by: Armin Le Grand <Armin.Le.Grand@cib.de>
2018-08-24 13:01:08 +02:00
AddPolygonToPath(aPath, aPolyLine, aPolyLine.isClosed(), !getAntiAliasB2DDraw(), true);
Qt5Painter aPainter(*this, false, 255 * (1.0 - fTransparency));
// setup line attributes
QPen aPen = aPainter.pen();
Support buffering SystemDependent GraphicData This is a first step to allow buffering of system dependent data, especially (but not only) for the system-dependent implementations of graphic output. For example, for B2DPolygon and Win output, it allows buffering the Gdiplus::GraphicsPath instead of re- creating it all the time. To support that, the change includes forwarding the current transformation to the renderers in SalGraphics. The current state in VCL is to transform all and everything to device coordinates at every single paint. I have currently started to do this for ::drawPolyLine implementations. The fallbacks for all systems will at the start of that method just transform the data to device coordinates, so all works as before. This may also be done for FilledPolygon paint in a later step, but most urgent is FatLine painting. An arrangement of shared_ptr/weak_ptr is used so that either the instance buffering (in the example B2DPolygon) or the instance managing it can delete it. The instance managing it currently uses a 1s Timer and a cycle-lifetime management, but that can be extended in the future to e.g. include size hints, too. The mechanism it designed to support multiple Data per buffering element, e.g. for B2DPolygon at the same time system-dependent instances of Gdiplus and Cairo can be buffered, but also PDF-data. This is achieved semi-automatic by using typeid(class).hash_code() as key for organization. The mechanism will be used for now at B2DPolygon, but is not limited to. There is already a similar but less general buffer (see GdiPlusBuffer) that can and will be converted to use this new mechanism. Added vcl/headless Cairo renderer to support given ObjectToDevice transformation (not to transform given B2DPolygon) Added support for CairoPath buffered at B2DPolygon, seems to work well. Need to do more tests Moved usage to templates suggested by Noel Grandin (Noel Grandin <noelgrandin@gmail.com>), thanks for these suggestions. Adapted Win usage to that, too. Converted Win-specific GdiPlus BitmapBuffer to new mechanism, works well. Checked, the manager holds now a mix of bitmap and path data under Win Added a cleanup mechanism to flush all buffered data at DeInitVCL() using flushAll() at SystemDependentDataBuffer Adapted Linux-versions of ::drawPolyLine to support PixelSnapHairline, for now in a simplified version that still allows buffering. This will also be used (and use buffering) for the Cairo-fallback in X11SalGraphics Change-Id: I88d7e438a20b96ddab7707050893bdd590c098c7 Reviewed-on: https://gerrit.libreoffice.org/59555 Tested-by: Armin Le Grand <Armin.Le.Grand@cib.de> Reviewed-by: Armin Le Grand <Armin.Le.Grand@cib.de>
2018-08-24 13:01:08 +02:00
aPen.setWidth(aLineWidths.getX());
switch (eLineJoin)
{
case basegfx::B2DLineJoin::Bevel:
aPen.setJoinStyle(Qt::BevelJoin);
break;
case basegfx::B2DLineJoin::Round:
aPen.setJoinStyle(Qt::RoundJoin);
break;
case basegfx::B2DLineJoin::NONE:
case basegfx::B2DLineJoin::Miter:
aPen.setMiterLimit(1.0 / sin(fMiterMinimumAngle / 2.0));
aPen.setJoinStyle(Qt::MiterJoin);
break;
}
switch (eLineCap)
{
default: // css::drawing::LineCap_BUTT:
aPen.setCapStyle(Qt::FlatCap);
break;
case css::drawing::LineCap_ROUND:
aPen.setCapStyle(Qt::RoundCap);
break;
case css::drawing::LineCap_SQUARE:
aPen.setCapStyle(Qt::SquareCap);
break;
}
aPainter.setPen(aPen);
aPainter.drawPath(aPath);
aPainter.update(aPath.boundingRect());
return true;
}
bool Qt5Graphics::drawGradient(const tools::PolyPolygon&, const Gradient&) { return false; }
void Qt5Graphics::drawScaledImage(const SalTwoRect& rPosAry, const QImage& rImage)
{
Qt5Painter aPainter(*this);
QRect aSrcRect(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
QRect aDestRect(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
aPainter.drawImage(aDestRect, rImage, aSrcRect);
aPainter.update(aDestRect);
}
void Qt5Graphics::copyArea(long nDestX, long nDestY, long nSrcX, long nSrcY, long nSrcWidth,
long nSrcHeight, bool /*bWindowInvalidate*/)
{
if (nDestX == nSrcX && nDestY == nSrcY)
return;
SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
copyBits(aTR, this);
}
void Qt5Graphics::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
{
if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
|| rPosAry.mnDestHeight <= 0)
return;
QImage aImage, *pImage;
SalTwoRect aPosAry = rPosAry;
if (!pSrcGraphics || this == pSrcGraphics)
{
pImage = m_pQImage;
aImage
= pImage->copy(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
pImage = &aImage;
aPosAry.mnSrcX = 0;
aPosAry.mnSrcY = 0;
}
else
pImage = static_cast<Qt5Graphics*>(pSrcGraphics)->m_pQImage;
drawScaledImage(aPosAry, *pImage);
}
void Qt5Graphics::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
{
if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
|| rPosAry.mnDestHeight <= 0)
return;
Qt5Bitmap aRGBABitmap;
if (rSalBitmap.GetBitCount() == 4)
aRGBABitmap.Create(rSalBitmap, 32);
const QImage* pImage = (rSalBitmap.GetBitCount() != 4)
? static_cast<const Qt5Bitmap*>(&rSalBitmap)->GetQImage()
: aRGBABitmap.GetQImage();
assert(pImage);
drawScaledImage(rPosAry, *pImage);
}
void Qt5Graphics::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& /*rSalBitmap*/,
const SalBitmap& /*rTransparentBitmap*/)
{
if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
|| rPosAry.mnDestHeight <= 0)
return;
assert(rPosAry.mnSrcWidth == rPosAry.mnDestWidth);
assert(rPosAry.mnSrcHeight == rPosAry.mnDestHeight);
}
void Qt5Graphics::drawMask(const SalTwoRect& rPosAry, const SalBitmap& /*rSalBitmap*/,
Color /*nMaskColor*/)
{
if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
|| rPosAry.mnDestHeight <= 0)
return;
assert(rPosAry.mnSrcWidth == rPosAry.mnDestWidth);
assert(rPosAry.mnSrcHeight == rPosAry.mnDestHeight);
}
std::shared_ptr<SalBitmap> Qt5Graphics::getBitmap(long nX, long nY, long nWidth, long nHeight)
{
return std::make_shared<Qt5Bitmap>(m_pQImage->copy(nX, nY, nWidth, nHeight));
}
Color Qt5Graphics::getPixel(long nX, long nY) { return m_pQImage->pixel(nX, nY); }
void Qt5Graphics::invert(long nX, long nY, long nWidth, long nHeight, SalInvert nFlags)
{
Qt5Painter aPainter(*this);
if (SalInvert::N50 & nFlags)
{
aPainter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
QBrush aBrush(Qt::white, Qt::Dense4Pattern);
aPainter.fillRect(nX, nY, nWidth, nHeight, aBrush);
}
else
{
if (SalInvert::TrackFrame & nFlags)
{
aPainter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
QPen aPen(Qt::white);
aPen.setStyle(Qt::DotLine);
aPainter.setPen(aPen);
aPainter.drawRect(nX, nY, nWidth, nHeight);
}
else
{
aPainter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
aPainter.fillRect(nX, nY, nWidth, nHeight, Qt::white);
}
}
aPainter.update(nX, nY, nWidth, nHeight);
}
void Qt5Graphics::invert(sal_uInt32 /*nPoints*/, const SalPoint* /*pPtAry*/, SalInvert /*nFlags*/)
{
}
bool Qt5Graphics::drawEPS(long /*nX*/, long /*nY*/, long /*nWidth*/, long /*nHeight*/,
void* /*pPtr*/, sal_uInt32 /*nSize*/)
{
return false;
}
bool Qt5Graphics::blendBitmap(const SalTwoRect&, const SalBitmap& /*rBitmap*/) { return false; }
bool Qt5Graphics::blendAlphaBitmap(const SalTwoRect&, const SalBitmap& /*rSrcBitmap*/,
const SalBitmap& /*rMaskBitmap*/,
const SalBitmap& /*rAlphaBitmap*/)
{
return false;
}
static bool getAlphaImage(const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap,
QImage& rAlphaImage)
{
if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1)
{
SAL_WARN("vcl.gdi", "unsupported alpha depth case: " << rAlphaBitmap.GetBitCount());
return false;
}
Qt5Bitmap aRGBABitmap;
if (rSourceBitmap.GetBitCount() == 4)
aRGBABitmap.Create(rSourceBitmap, 32);
const QImage* pBitmap = (rSourceBitmap.GetBitCount() != 4)
? static_cast<const Qt5Bitmap*>(&rSourceBitmap)->GetQImage()
: aRGBABitmap.GetQImage();
const QImage* pAlpha = static_cast<const Qt5Bitmap*>(&rAlphaBitmap)->GetQImage();
rAlphaImage = pBitmap->convertToFormat(Qt5_DefaultFormat32);
if (rAlphaBitmap.GetBitCount() == 8)
{
for (int y = 0; y < rAlphaImage.height(); ++y)
{
uchar* image_line = rAlphaImage.scanLine(y);
const uchar* alpha_line = pAlpha->scanLine(y);
for (int x = 0; x < rAlphaImage.width(); ++x, image_line += 4)
image_line[3] = 255 - alpha_line[x];
}
}
else
{
for (int y = 0; y < rAlphaImage.height(); ++y)
{
uchar* image_line = rAlphaImage.scanLine(y);
const uchar* alpha_line = pAlpha->scanLine(y);
for (int x = 0; x < rAlphaImage.width(); ++x, image_line += 4)
{
if (x && !(x % 8))
++alpha_line;
if (0 != (*alpha_line & (1 << (7 - x % 8))))
image_line[3] = 0;
}
}
}
return true;
}
bool Qt5Graphics::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
const SalBitmap& rAlphaBitmap)
{
QImage aImage;
if (!getAlphaImage(rSourceBitmap, rAlphaBitmap, aImage))
return false;
drawScaledImage(rPosAry, aImage);
return true;
}
bool Qt5Graphics::drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
const SalBitmap* pAlphaBitmap)
{
QImage aImage;
if (pAlphaBitmap && !getAlphaImage(rSourceBitmap, *pAlphaBitmap, aImage))
return false;
else
{
Qt5Bitmap aRGBABitmap;
if (rSourceBitmap.GetBitCount() == 4)
aRGBABitmap.Create(rSourceBitmap, 32);
const QImage* pBitmap = (rSourceBitmap.GetBitCount() != 4)
? static_cast<const Qt5Bitmap*>(&rSourceBitmap)->GetQImage()
: aRGBABitmap.GetQImage();
aImage = pBitmap->convertToFormat(Qt5_DefaultFormat32);
}
Qt5Painter aPainter(*this);
const basegfx::B2DVector aXRel = rX - rNull;
const basegfx::B2DVector aYRel = rY - rNull;
aPainter.setTransform(QTransform(aXRel.getX() / aImage.width(), aXRel.getY() / aImage.width(),
aYRel.getX() / aImage.height(), aYRel.getY() / aImage.height(),
rNull.getX(), rNull.getY()));
aPainter.drawImage(QPoint(0, 0), aImage);
aPainter.update(aImage.rect());
return true;
}
bool Qt5Graphics::drawAlphaRect(long nX, long nY, long nWidth, long nHeight,
sal_uInt8 nTransparency)
{
if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
return true;
assert(nTransparency <= 100);
if (nTransparency > 100)
nTransparency = 100;
Qt5Painter aPainter(*this, true, (100 - nTransparency) * (255.0 / 100));
if (SALCOLOR_NONE != m_aFillColor)
aPainter.fillRect(nX, nY, nWidth, nHeight, aPainter.brush());
if (SALCOLOR_NONE != m_aLineColor)
aPainter.drawRect(nX, nY, nWidth, nHeight);
aPainter.update(nX, nY, nWidth, nHeight);
return true;
}
void Qt5Graphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
{
char* pForceDpi;
if ((pForceDpi = getenv("SAL_FORCEDPI")))
{
OString sForceDPI(pForceDpi);
rDPIX = rDPIY = sForceDPI.toInt32();
return;
}
if (!m_pFrame || !m_pFrame->GetQWidget()->window()->windowHandle())
return;
QScreen* pScreen = m_pFrame->GetQWidget()->window()->windowHandle()->screen();
rDPIX = pScreen->logicalDotsPerInchX() * pScreen->devicePixelRatio() + 0.5;
rDPIY = pScreen->logicalDotsPerInchY() * pScreen->devicePixelRatio() + 0.5;
}
sal_uInt16 Qt5Graphics::GetBitCount() const { return getFormatBits(m_pQImage->format()); }
long Qt5Graphics::GetGraphicsWidth() const { return m_pQImage->width(); }
void Qt5Graphics::SetLineColor() { m_aLineColor = SALCOLOR_NONE; }
void Qt5Graphics::SetLineColor(Color nColor) { m_aLineColor = nColor; }
void Qt5Graphics::SetFillColor() { m_aFillColor = SALCOLOR_NONE; }
void Qt5Graphics::SetFillColor(Color nColor) { m_aFillColor = nColor; }
void Qt5Graphics::SetXORMode(bool bSet, bool)
{
if (bSet)
m_eCompositionMode = QPainter::CompositionMode_Xor;
else
m_eCompositionMode = QPainter::CompositionMode_SourceOver;
}
void Qt5Graphics::SetROPLineColor(SalROPColor /*nROPColor*/) {}
void Qt5Graphics::SetROPFillColor(SalROPColor /*nROPColor*/) {}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */