mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Finalization of axial shading
This commit is contained in:
@ -275,6 +275,40 @@ PDFColor PDFAbstractColorSpace::convertToColor(const std::vector<PDFReal>& compo
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PDFAbstractColorSpace::isColorEqual(const PDFColor& color1, const PDFColor& color2, PDFReal tolerance)
|
||||||
|
{
|
||||||
|
const size_t size = color1.size();
|
||||||
|
if (size != color2.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
if (std::fabs(color1[i] - color2[i]) > tolerance)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFColor PDFAbstractColorSpace::mixColors(const PDFColor& color1, const PDFColor& color2, PDFReal ratio)
|
||||||
|
{
|
||||||
|
const size_t size = color1.size();
|
||||||
|
Q_ASSERT(size == color2.size());
|
||||||
|
|
||||||
|
PDFColor result;
|
||||||
|
result.resize(size);
|
||||||
|
for (size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
result[i] = color1[i] * ratio + color2[i] * (1.0 - ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary,
|
PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary,
|
||||||
const PDFDocument* document,
|
const PDFDocument* document,
|
||||||
const PDFObject& colorSpace,
|
const PDFObject& colorSpace,
|
||||||
|
@ -236,6 +236,20 @@ public:
|
|||||||
/// Converts a vector of real numbers to the PDFColor
|
/// Converts a vector of real numbers to the PDFColor
|
||||||
static PDFColor convertToColor(const std::vector<PDFReal>& components);
|
static PDFColor convertToColor(const std::vector<PDFReal>& components);
|
||||||
|
|
||||||
|
/// Returns true, if two colors are equal (considering the tolerance). So, if one
|
||||||
|
/// of the color components differs more than \p tolerance from the another, then
|
||||||
|
/// false is returned. If colors have different number of components, false is returned.
|
||||||
|
/// \param color1 First color
|
||||||
|
/// \param color2 Second color
|
||||||
|
/// \param tolerance Color tolerance
|
||||||
|
static bool isColorEqual(const PDFColor& color1, const PDFColor& color2, PDFReal tolerance);
|
||||||
|
|
||||||
|
/// Mix colors according the given ratio.
|
||||||
|
/// \param color1 First color
|
||||||
|
/// \param color2 Second color
|
||||||
|
/// \param ratio Mixing ratio
|
||||||
|
static PDFColor mixColors(const PDFColor& color1, const PDFColor& color2, PDFReal ratio);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Clips the color component to range [0, 1]
|
/// Clips the color component to range [0, 1]
|
||||||
static constexpr PDFColorComponent clip01(PDFColorComponent component) { return qBound<PDFColorComponent>(0.0, component, 1.0); }
|
static constexpr PDFColorComponent clip01(PDFColorComponent component) { return qBound<PDFColorComponent>(0.0, component, 1.0); }
|
||||||
|
@ -158,6 +158,25 @@ public:
|
|||||||
/// Erases the last element from the array
|
/// Erases the last element from the array
|
||||||
inline void pop_back() { resize(size() - 1); }
|
inline void pop_back() { resize(size() - 1); }
|
||||||
|
|
||||||
|
bool operator==(const PDFFlatArray& other) const
|
||||||
|
{
|
||||||
|
const size_t size = this->size();
|
||||||
|
if (size != other.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
if ((*this)[i] != other[i])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t getFlatBlockSize() const { return m_flatBlockItemCount; }
|
size_t getFlatBlockSize() const { return m_flatBlockItemCount; }
|
||||||
|
|
||||||
|
@ -42,12 +42,18 @@ using PDFReal = double;
|
|||||||
|
|
||||||
constexpr PDFInteger PDF_INTEGER_MIN = std::numeric_limits<int64_t>::min() / 100;
|
constexpr PDFInteger PDF_INTEGER_MIN = std::numeric_limits<int64_t>::min() / 100;
|
||||||
constexpr PDFInteger PDF_INTEGER_MAX = std::numeric_limits<int64_t>::max() / 100;
|
constexpr PDFInteger PDF_INTEGER_MAX = std::numeric_limits<int64_t>::max() / 100;
|
||||||
|
constexpr PDFReal PDF_EPSILON = 0.000001;
|
||||||
|
|
||||||
static constexpr bool isValidInteger(PDFInteger integer)
|
static constexpr bool isValidInteger(PDFInteger integer)
|
||||||
{
|
{
|
||||||
return integer >= PDF_INTEGER_MIN && integer <= PDF_INTEGER_MAX;
|
return integer >= PDF_INTEGER_MIN && integer <= PDF_INTEGER_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool isZero(PDFReal value)
|
||||||
|
{
|
||||||
|
return std::fabs(value) < PDF_EPSILON;
|
||||||
|
}
|
||||||
|
|
||||||
/// This structure represents a reference to the object - consisting of the
|
/// This structure represents a reference to the object - consisting of the
|
||||||
/// object number, and generation number.
|
/// object number, and generation number.
|
||||||
struct PDFObjectReference
|
struct PDFObjectReference
|
||||||
|
@ -181,7 +181,7 @@ PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page,
|
|||||||
const PDFDocument* document,
|
const PDFDocument* document,
|
||||||
const PDFFontCache* fontCache,
|
const PDFFontCache* fontCache,
|
||||||
const PDFOptionalContentActivity* optionalContentActivity,
|
const PDFOptionalContentActivity* optionalContentActivity,
|
||||||
QMatrix patternBaseMatrix) :
|
QMatrix pagePointToDevicePointMatrix) :
|
||||||
m_page(page),
|
m_page(page),
|
||||||
m_document(document),
|
m_document(document),
|
||||||
m_fontCache(fontCache),
|
m_fontCache(fontCache),
|
||||||
@ -194,14 +194,15 @@ PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page,
|
|||||||
m_shadingDictionary(nullptr),
|
m_shadingDictionary(nullptr),
|
||||||
m_textBeginEndState(0),
|
m_textBeginEndState(0),
|
||||||
m_compatibilityBeginEndState(0),
|
m_compatibilityBeginEndState(0),
|
||||||
m_patternBaseMatrix(patternBaseMatrix)
|
m_patternBaseMatrix(pagePointToDevicePointMatrix),
|
||||||
|
m_pagePointToDevicePointMatrix(pagePointToDevicePointMatrix)
|
||||||
{
|
{
|
||||||
Q_ASSERT(page);
|
Q_ASSERT(page);
|
||||||
Q_ASSERT(document);
|
Q_ASSERT(document);
|
||||||
|
|
||||||
QPainterPath pageRectPath;
|
QPainterPath pageRectPath;
|
||||||
pageRectPath.addRect(m_page->getRotatedMediaBox());
|
pageRectPath.addRect(m_page->getRotatedMediaBox());
|
||||||
m_pageBoundingRectDeviceSpace = patternBaseMatrix.map(pageRectPath).boundingRect();
|
m_pageBoundingRectDeviceSpace = pagePointToDevicePointMatrix.map(pageRectPath).boundingRect();
|
||||||
|
|
||||||
initDictionaries(m_page->getResources());
|
initDictionaries(m_page->getResources());
|
||||||
}
|
}
|
||||||
@ -548,7 +549,8 @@ void PDFPageContentProcessor::processForm(const QMatrix& matrix, const QRectF& b
|
|||||||
m_graphicState.setCurrentTransformationMatrix(formMatrix);
|
m_graphicState.setCurrentTransformationMatrix(formMatrix);
|
||||||
updateGraphicState();
|
updateGraphicState();
|
||||||
|
|
||||||
PDFTemporaryValueChange patternMatrixGuard(&m_patternBaseMatrix, formMatrix);
|
QMatrix patternMatrix = formMatrix * m_pagePointToDevicePointMatrix;
|
||||||
|
PDFTemporaryValueChange patternMatrixGuard(&m_patternBaseMatrix, patternMatrix);
|
||||||
|
|
||||||
// If the clipping box is valid, then use clipping. Clipping box is in the form coordinate system
|
// If the clipping box is valid, then use clipping. Clipping box is in the form coordinate system
|
||||||
if (boundingBox.isValid())
|
if (boundingBox.isValid())
|
||||||
@ -2044,7 +2046,7 @@ void PDFPageContentProcessor::operatorTextSetSpacingAndShowText(PDFReal t_w, PDF
|
|||||||
|
|
||||||
void PDFPageContentProcessor::operatorShadingPaintShape(PDFPageContentProcessor::PDFOperandName name)
|
void PDFPageContentProcessor::operatorShadingPaintShape(PDFPageContentProcessor::PDFOperandName name)
|
||||||
{
|
{
|
||||||
QMatrix matrix = getGraphicState()->getCurrentTransformationMatrix();
|
QMatrix matrix = getCurrentWorldMatrix();
|
||||||
PDFPageContentProcessorStateGuard guard(this);
|
PDFPageContentProcessorStateGuard guard(this);
|
||||||
PDFTemporaryValueChange guard2(&m_patternBaseMatrix, matrix);
|
PDFTemporaryValueChange guard2(&m_patternBaseMatrix, matrix);
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ public:
|
|||||||
const PDFDocument* document,
|
const PDFDocument* document,
|
||||||
const PDFFontCache* fontCache,
|
const PDFFontCache* fontCache,
|
||||||
const PDFOptionalContentActivity* optionalContentActivity,
|
const PDFOptionalContentActivity* optionalContentActivity,
|
||||||
QMatrix patternBaseMatrix);
|
QMatrix pagePointToDevicePointMatrix);
|
||||||
virtual ~PDFPageContentProcessor();
|
virtual ~PDFPageContentProcessor();
|
||||||
|
|
||||||
enum class Operator
|
enum class Operator
|
||||||
@ -413,6 +413,18 @@ protected:
|
|||||||
/// Returns true, if graphic content is suppressed
|
/// Returns true, if graphic content is suppressed
|
||||||
bool isContentSuppressed() const;
|
bool isContentSuppressed() const;
|
||||||
|
|
||||||
|
/// Returns page point to device point matrix
|
||||||
|
const QMatrix& getPagePointToDevicePointMatrix() const { return m_pagePointToDevicePointMatrix; }
|
||||||
|
|
||||||
|
/// Returns base matrix for patterns
|
||||||
|
const QMatrix& getPatternBaseMatrix() const { return m_patternBaseMatrix; }
|
||||||
|
|
||||||
|
/// Returns current world matrix (translating actual point to the device point)
|
||||||
|
QMatrix getCurrentWorldMatrix() const { return getGraphicState()->getCurrentTransformationMatrix() * m_pagePointToDevicePointMatrix; }
|
||||||
|
|
||||||
|
/// Returns page bounding rectangle in device space
|
||||||
|
const QRectF& getPageBoundingRectDeviceSpace() const { return m_pageBoundingRectDeviceSpace; }
|
||||||
|
|
||||||
/// Computes visibility of OCG/OCMD - returns false, if it is not suppressed,
|
/// Computes visibility of OCG/OCMD - returns false, if it is not suppressed,
|
||||||
/// or true, if it is suppressed.
|
/// or true, if it is suppressed.
|
||||||
virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd);
|
virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd);
|
||||||
@ -738,6 +750,9 @@ private:
|
|||||||
/// with pattern matrix to get transformation from pattern space to device space.
|
/// with pattern matrix to get transformation from pattern space to device space.
|
||||||
QMatrix m_patternBaseMatrix;
|
QMatrix m_patternBaseMatrix;
|
||||||
|
|
||||||
|
/// Matrix mapping page points to the device points
|
||||||
|
QMatrix m_pagePointToDevicePointMatrix;
|
||||||
|
|
||||||
/// Bounding rectangle of pages media box in device space coordinates. If drawing rotation
|
/// Bounding rectangle of pages media box in device space coordinates. If drawing rotation
|
||||||
/// is zero, then it corresponds to the scaled media box of the page.
|
/// is zero, then it corresponds to the scaled media box of the page.
|
||||||
QRectF m_pageBoundingRectDeviceSpace;
|
QRectF m_pageBoundingRectDeviceSpace;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include "pdfpainter.h"
|
#include "pdfpainter.h"
|
||||||
|
#include "pdfpattern.h"
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
@ -31,8 +32,7 @@ PDFPainter::PDFPainter(QPainter* painter,
|
|||||||
const PDFOptionalContentActivity* optionalContentActivity) :
|
const PDFOptionalContentActivity* optionalContentActivity) :
|
||||||
PDFPageContentProcessor(page, document, fontCache, optionalContentActivity, pagePointToDevicePointMatrix),
|
PDFPageContentProcessor(page, document, fontCache, optionalContentActivity, pagePointToDevicePointMatrix),
|
||||||
m_painter(painter),
|
m_painter(painter),
|
||||||
m_features(features),
|
m_features(features)
|
||||||
m_pagePointToDevicePointMatrix(pagePointToDevicePointMatrix)
|
|
||||||
{
|
{
|
||||||
Q_ASSERT(painter);
|
Q_ASSERT(painter);
|
||||||
Q_ASSERT(pagePointToDevicePointMatrix.isInvertible());
|
Q_ASSERT(pagePointToDevicePointMatrix.isInvertible());
|
||||||
@ -60,6 +60,24 @@ void PDFPainter::performPathPainting(const QPainterPath& path, bool stroke, bool
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Temporary
|
||||||
|
if (const PDFPatternColorSpace* ps = dynamic_cast<const PDFPatternColorSpace*>(getGraphicState()->getFillColorSpace()))
|
||||||
|
{
|
||||||
|
m_painter->save();
|
||||||
|
const PDFAxialShading* pattern = (PDFAxialShading*)ps->getPattern();
|
||||||
|
m_painter->setClipPath(path, Qt::IntersectClip);
|
||||||
|
|
||||||
|
PDFMeshQualitySettings settings;
|
||||||
|
settings.deviceSpaceMeshingArea = getPageBoundingRectDeviceSpace();
|
||||||
|
settings.userSpaceToDeviceSpaceMatrix = getPatternBaseMatrix();
|
||||||
|
settings.initDefaultResolution();
|
||||||
|
|
||||||
|
PDFMesh mesh = pattern->createMesh(settings);
|
||||||
|
mesh.paint(m_painter);
|
||||||
|
m_painter->restore();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set antialiasing
|
// Set antialiasing
|
||||||
const bool antialiasing = (text && m_features.testFlag(PDFRenderer::TextAntialiasing)) || (!text && m_features.testFlag(PDFRenderer::Antialiasing));
|
const bool antialiasing = (text && m_features.testFlag(PDFRenderer::TextAntialiasing)) || (!text && m_features.testFlag(PDFRenderer::Antialiasing));
|
||||||
m_painter->setRenderHint(QPainter::Antialiasing, antialiasing);
|
m_painter->setRenderHint(QPainter::Antialiasing, antialiasing);
|
||||||
@ -153,7 +171,7 @@ void PDFPainter::performUpdateGraphicsState(const PDFPageContentProcessorState&
|
|||||||
// If current transformation matrix has changed, then update it
|
// If current transformation matrix has changed, then update it
|
||||||
if (flags.testFlag(PDFPageContentProcessorState::StateCurrentTransformationMatrix))
|
if (flags.testFlag(PDFPageContentProcessorState::StateCurrentTransformationMatrix))
|
||||||
{
|
{
|
||||||
m_painter->setWorldMatrix(state.getCurrentTransformationMatrix() * m_pagePointToDevicePointMatrix, false);
|
m_painter->setWorldMatrix(getCurrentWorldMatrix(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.testFlag(PDFPageContentProcessorState::StateStrokeColor) ||
|
if (flags.testFlag(PDFPageContentProcessorState::StateStrokeColor) ||
|
||||||
|
@ -74,7 +74,6 @@ private:
|
|||||||
|
|
||||||
QPainter* m_painter;
|
QPainter* m_painter;
|
||||||
PDFRenderer::Features m_features;
|
PDFRenderer::Features m_features;
|
||||||
QMatrix m_pagePointToDevicePointMatrix;
|
|
||||||
PDFCachedItem<QPen> m_currentPen;
|
PDFCachedItem<QPen> m_currentPen;
|
||||||
PDFCachedItem<QBrush> m_currentBrush;
|
PDFCachedItem<QBrush> m_currentBrush;
|
||||||
};
|
};
|
||||||
|
@ -18,6 +18,10 @@
|
|||||||
#include "pdfpattern.h"
|
#include "pdfpattern.h"
|
||||||
#include "pdfdocument.h"
|
#include "pdfdocument.h"
|
||||||
#include "pdfexception.h"
|
#include "pdfexception.h"
|
||||||
|
#include "pdfutils.h"
|
||||||
|
#include "pdfcolorspaces.h"
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
@ -186,4 +190,261 @@ PDFPatternPtr PDFPattern::createShadingPattern(const PDFDictionary* colorSpaceDi
|
|||||||
return PDFPatternPtr();
|
return PDFPatternPtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PDFMesh PDFAxialShading::createMesh(const PDFMeshQualitySettings& settings) const
|
||||||
|
{
|
||||||
|
PDFMesh mesh;
|
||||||
|
|
||||||
|
QPointF p1 = settings.userSpaceToDeviceSpaceMatrix.map(m_startPoint);
|
||||||
|
QPointF p2 = settings.userSpaceToDeviceSpaceMatrix.map(m_endPoint);
|
||||||
|
|
||||||
|
// Strategy: for simplification, we rotate the line clockwise so we will
|
||||||
|
// get the shading axis equal to the x-axis. Then we will determine the shading
|
||||||
|
// area and create mesh according the settings.
|
||||||
|
QLineF line(p1, p2);
|
||||||
|
const double angle = line.angleTo(QLineF(0, 0, 1, 0));
|
||||||
|
|
||||||
|
// Matrix p1p2LCS is local coordinate system of line p1-p2. It transforms
|
||||||
|
// points on the line to the global coordinate system. So, point (0, 0) will
|
||||||
|
// map onto p1 and point (length(p1-p2), 0) will map onto p2.
|
||||||
|
QMatrix p1p2LCS;
|
||||||
|
p1p2LCS.translate(p1.x(), p1.y());
|
||||||
|
p1p2LCS.rotate(angle);
|
||||||
|
QMatrix p1p2GCS = p1p2LCS.inverted();
|
||||||
|
|
||||||
|
QPointF p1m = p1p2GCS.map(p1);
|
||||||
|
QPointF p2m = p1p2GCS.map(p2);
|
||||||
|
|
||||||
|
Q_ASSERT(isZero(p1m.y()));
|
||||||
|
Q_ASSERT(isZero(p2m.y()));
|
||||||
|
Q_ASSERT(p1m.x() <= p2m.x());
|
||||||
|
|
||||||
|
QPainterPath meshingArea;
|
||||||
|
meshingArea.addPolygon(p1p2GCS.map(settings.deviceSpaceMeshingArea));
|
||||||
|
meshingArea.addRect(p1m.x(), p1m.y() - settings.preferredMeshResolution * 0.5, p2m.x() - p1m.x(), settings.preferredMeshResolution);
|
||||||
|
QRectF meshingRectangle = meshingArea.boundingRect();
|
||||||
|
|
||||||
|
PDFReal xl = meshingRectangle.left();
|
||||||
|
PDFReal xr = meshingRectangle.right();
|
||||||
|
PDFReal yt = meshingRectangle.top();
|
||||||
|
PDFReal yb = meshingRectangle.bottom();
|
||||||
|
|
||||||
|
// Create coordinate array filled with stops, where we will determine the color
|
||||||
|
std::vector<PDFReal> xCoords;
|
||||||
|
xCoords.reserve((xr - xl) / settings.minimalMeshResolution + 3);
|
||||||
|
xCoords.push_back(xl);
|
||||||
|
for (PDFReal x = p1m.x(); x <= p2m.x(); x += settings.minimalMeshResolution)
|
||||||
|
{
|
||||||
|
if (!qFuzzyCompare(xCoords.back(), x))
|
||||||
|
{
|
||||||
|
xCoords.push_back(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!qFuzzyCompare(xCoords.back(), xr))
|
||||||
|
{
|
||||||
|
xCoords.push_back(xr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const PDFReal tAtStart = m_domainStart;
|
||||||
|
const PDFReal tAtEnd = m_domainEnd;
|
||||||
|
const PDFReal tMin = qMin(tAtStart, tAtEnd);
|
||||||
|
const PDFReal tMax = qMax(tAtStart, tAtEnd);
|
||||||
|
|
||||||
|
const bool isSingleFunction = m_functions.size() == 1;
|
||||||
|
std::vector<PDFReal> colorBuffer(m_colorSpace->getColorComponentCount(), 0.0);
|
||||||
|
auto getColor = [this, isSingleFunction, &colorBuffer](PDFReal t) -> PDFColor
|
||||||
|
{
|
||||||
|
if (isSingleFunction)
|
||||||
|
{
|
||||||
|
PDFFunction::FunctionResult result = m_functions.front()->apply(&t, &t + 1, colorBuffer.data(), colorBuffer.data() + colorBuffer.size());
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Error occured during mesh creation of shading: %1").arg(result.errorMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (size_t i = 0, count = colorBuffer.size(); i < count; ++i)
|
||||||
|
{
|
||||||
|
PDFFunction::FunctionResult result = m_functions.front()->apply(&t, &t + 1, colorBuffer.data() + i, colorBuffer.data() + i + 1);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Error occured during mesh creation of shading: %1").arg(result.errorMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PDFAbstractColorSpace::convertToColor(colorBuffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine color of each coordinate
|
||||||
|
std::vector<std::pair<PDFReal, PDFColor>> coloredCoordinates;
|
||||||
|
coloredCoordinates.reserve(xCoords.size());
|
||||||
|
|
||||||
|
for (PDFReal x : xCoords)
|
||||||
|
{
|
||||||
|
if (x < p1m.x() - PDF_EPSILON && !m_extendStart)
|
||||||
|
{
|
||||||
|
// Move to the next coordinate, this is skipped
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x > p2m.x() + PDF_EPSILON && !m_extendEnd)
|
||||||
|
{
|
||||||
|
// We are finished no more triangles will occur
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine current parameter t
|
||||||
|
const PDFReal t = interpolate(x, p1m.x(), p2m.x(), tAtStart, tAtEnd);
|
||||||
|
const PDFReal tBounded = qBound(tMin, t, tMax);
|
||||||
|
const PDFColor color = getColor(tBounded);
|
||||||
|
coloredCoordinates.emplace_back(x, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter coordinates according the meshing criteria
|
||||||
|
std::vector<std::pair<PDFReal, PDFColor>> filteredCoordinates;
|
||||||
|
filteredCoordinates.reserve(coloredCoordinates.size());
|
||||||
|
|
||||||
|
for (auto it = coloredCoordinates.cbegin(); it != coloredCoordinates.cend(); ++it)
|
||||||
|
{
|
||||||
|
// We will skip this coordinate, if both of meshing criteria have been met:
|
||||||
|
// 1) Color difference is small (lesser than tolerance)
|
||||||
|
// 2) Distance from previous and next point is less than preffered meshing resolution OR colors are equal
|
||||||
|
|
||||||
|
if (it != coloredCoordinates.cbegin() && std::next(it) != coloredCoordinates.cend())
|
||||||
|
{
|
||||||
|
auto itNext = std::next(it);
|
||||||
|
|
||||||
|
const std::pair<PDFReal, PDFColor>& prevItem = filteredCoordinates.back();
|
||||||
|
const std::pair<PDFReal, PDFColor>& currentItem = *it;
|
||||||
|
const std::pair<PDFReal, PDFColor>& nextItem = *itNext;
|
||||||
|
|
||||||
|
if (prevItem.second == currentItem.second && currentItem.second == nextItem.second)
|
||||||
|
{
|
||||||
|
// Colors are same, skip the test
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PDFAbstractColorSpace::isColorEqual(prevItem.second, currentItem.second, settings.tolerance) &&
|
||||||
|
PDFAbstractColorSpace::isColorEqual(currentItem.second, nextItem.second, settings.tolerance) &&
|
||||||
|
PDFAbstractColorSpace::isColorEqual(prevItem.second, nextItem.second, settings.tolerance) &&
|
||||||
|
(nextItem.first - prevItem.first < settings.preferredMeshResolution))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredCoordinates.push_back(*it);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filteredCoordinates.empty())
|
||||||
|
{
|
||||||
|
size_t vertexCount = filteredCoordinates.size() * 2;
|
||||||
|
size_t triangleCount = filteredCoordinates.size() * 2 - 2;
|
||||||
|
|
||||||
|
if (m_backgroundColor.isValid())
|
||||||
|
{
|
||||||
|
vertexCount += 8;
|
||||||
|
triangleCount += 4;
|
||||||
|
}
|
||||||
|
mesh.reserve(vertexCount, triangleCount);
|
||||||
|
|
||||||
|
PDFColor previousColor = filteredCoordinates.front().second;
|
||||||
|
uint32_t topLeft = mesh.addVertex(QPointF(filteredCoordinates.front().first, yt));
|
||||||
|
uint32_t bottomLeft = mesh.addVertex(QPointF(filteredCoordinates.front().first, yb));
|
||||||
|
for (auto it = std::next(filteredCoordinates.cbegin()); it != filteredCoordinates.cend(); ++it)
|
||||||
|
{
|
||||||
|
const std::pair<PDFReal, PDFColor>& item = *it;
|
||||||
|
|
||||||
|
uint32_t topRight = mesh.addVertex(QPointF(item.first, yt));
|
||||||
|
uint32_t bottomRight = mesh.addVertex(QPointF(item.first, yb));
|
||||||
|
|
||||||
|
PDFColor mixedColor = PDFAbstractColorSpace::mixColors(previousColor, item.second, 0.5);
|
||||||
|
QColor color = m_colorSpace->getColor(mixedColor);
|
||||||
|
mesh.addQuad(topLeft, topRight, bottomRight, bottomLeft, color.rgb());
|
||||||
|
|
||||||
|
topLeft = topRight;
|
||||||
|
bottomLeft = bottomRight;
|
||||||
|
previousColor = item.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create background color triangles
|
||||||
|
|
||||||
|
// Transform mesh to the device space coordinates
|
||||||
|
mesh.transform(p1p2LCS);
|
||||||
|
|
||||||
|
// Transform mesh from the device space coordinates to user space coordinates
|
||||||
|
Q_ASSERT(settings.userSpaceToDeviceSpaceMatrix.isInvertible());
|
||||||
|
QMatrix deviceSpaceToUserSpaceMatrix = settings.userSpaceToDeviceSpaceMatrix.inverted();
|
||||||
|
mesh.transform(deviceSpaceToUserSpaceMatrix);
|
||||||
|
|
||||||
|
// Create bounding path
|
||||||
|
if (m_boundingBox.isValid())
|
||||||
|
{
|
||||||
|
QPainterPath boundingPath;
|
||||||
|
boundingPath.addRect(m_boundingBox);
|
||||||
|
mesh.setBoundingPath(boundingPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFMesh::paint(QPainter* painter) const
|
||||||
|
{
|
||||||
|
if (m_triangles.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->save();
|
||||||
|
painter->setPen(Qt::NoPen);
|
||||||
|
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
|
||||||
|
// Set the clipping area, if we have it
|
||||||
|
if (!m_boundingPath.isEmpty())
|
||||||
|
{
|
||||||
|
painter->setClipPath(m_boundingPath, Qt::IntersectClip);
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor color;
|
||||||
|
|
||||||
|
// Draw all triangles
|
||||||
|
for (const Triangle& triangle : m_triangles)
|
||||||
|
{
|
||||||
|
if (color != triangle.color)
|
||||||
|
{
|
||||||
|
painter->setPen(QColor(triangle.color));
|
||||||
|
painter->setBrush(QBrush(triangle.color, Qt::SolidPattern));
|
||||||
|
color = triangle.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<QPointF, 3> triangleCorners = { m_vertices[triangle.v1], m_vertices[triangle.v2], m_vertices[triangle.v3] };
|
||||||
|
painter->drawConvexPolygon(triangleCorners.data(), static_cast<int>(triangleCorners.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFMesh::transform(const QMatrix& matrix)
|
||||||
|
{
|
||||||
|
for (QPointF& vertex : m_vertices)
|
||||||
|
{
|
||||||
|
vertex = matrix.map(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_boundingPath = matrix.map(m_boundingPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFMeshQualitySettings::initDefaultResolution()
|
||||||
|
{
|
||||||
|
// We will take 0.5% percent of device space meshing area as minimal resolution (it is ~1.5 mm for
|
||||||
|
// A4 page) and default resolution 4x number of that.
|
||||||
|
|
||||||
|
Q_ASSERT(deviceSpaceMeshingArea.isValid());
|
||||||
|
PDFReal size = qMax(deviceSpaceMeshingArea.width(), deviceSpaceMeshingArea.height());
|
||||||
|
minimalMeshResolution = size * 0.005;
|
||||||
|
preferredMeshResolution = minimalMeshResolution * 4;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -51,6 +51,87 @@ enum class ShadingType
|
|||||||
TensorProductPatchMesh = 7
|
TensorProductPatchMesh = 7
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PDFMeshQualitySettings
|
||||||
|
{
|
||||||
|
/// Initializes default resolution
|
||||||
|
void initDefaultResolution();
|
||||||
|
|
||||||
|
/// Matrix, which transforms user space points (user space is target space of the shading)
|
||||||
|
/// to the device space of the paint device.
|
||||||
|
QMatrix userSpaceToDeviceSpaceMatrix;
|
||||||
|
|
||||||
|
/// Rectangle in device space coordinate system, onto which is area meshed.
|
||||||
|
QRectF deviceSpaceMeshingArea;
|
||||||
|
|
||||||
|
/// Preferred mesh resolution in device space pixels. Mesh will be created in this
|
||||||
|
/// resolution, if it is smooth enough (no jumps in colors occurs).
|
||||||
|
PDFReal preferredMeshResolution = 1.0;
|
||||||
|
|
||||||
|
/// Minimal mesh resolution in device space pixels. If jumps in colors occurs (jump
|
||||||
|
/// is two colors, that differ more than \p color tolerance), then mesh is meshed to
|
||||||
|
/// minimal mesh resolution.
|
||||||
|
PDFReal minimalMeshResolution = 1.0;
|
||||||
|
|
||||||
|
/// Color tolerance - 1% by default
|
||||||
|
PDFReal tolerance = 0.01;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Mesh consisting of triangles
|
||||||
|
class PDFMesh
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit PDFMesh() = default;
|
||||||
|
|
||||||
|
struct Triangle
|
||||||
|
{
|
||||||
|
uint32_t v1 = 0;
|
||||||
|
uint32_t v2 = 0;
|
||||||
|
uint32_t v3 = 0;
|
||||||
|
|
||||||
|
QRgb color;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Adds vertex. Returns index of added vertex.
|
||||||
|
/// \param vertex Vertex to be added
|
||||||
|
/// \returns Index of the added vertex
|
||||||
|
inline uint32_t addVertex(const QPointF& vertex) { const size_t index = m_vertices.size(); m_vertices.emplace_back(vertex); return static_cast<uint32_t>(index); }
|
||||||
|
|
||||||
|
/// Adds triangle. Returns index of added triangle.
|
||||||
|
/// \param triangle Triangle to be added
|
||||||
|
/// \returns Index of the added vertex
|
||||||
|
inline uint32_t addTriangle(const Triangle& triangle) { const size_t index = m_triangles.size(); m_triangles.emplace_back(triangle); return static_cast<uint32_t>(index); }
|
||||||
|
|
||||||
|
/// Adds quad. Vertices are in clockwise order (so, we have edges v1-v2, v2-v3, v3-v4, v4-v1).
|
||||||
|
/// \param v1 First vertex (for example, topleft)
|
||||||
|
/// \param v2 Second vertex (for example, topright)
|
||||||
|
/// \param v3 Third vertex (for example, bottomright)
|
||||||
|
/// \param v4 Fourth vertex (for example, bottomleft)
|
||||||
|
/// \param color Color of the quad.
|
||||||
|
inline void addQuad(uint32_t v1, uint32_t v2, uint32_t v3, uint32_t v4, QRgb color) { addTriangle({v1, v2, v3, color}); addTriangle({ v1, v3, v4, color}); }
|
||||||
|
|
||||||
|
/// Paints the mesh on the painter
|
||||||
|
/// \param painter Painter, onto which is mesh drawn
|
||||||
|
void paint(QPainter* painter) const;
|
||||||
|
|
||||||
|
/// Transforms the mesh according to the matrix transform
|
||||||
|
/// \param matrix Matrix transform to be performed
|
||||||
|
void transform(const QMatrix& matrix);
|
||||||
|
|
||||||
|
/// Reserves memory for meshing - both number of vertices and triangles.
|
||||||
|
/// Use this function, if number of vertices and triangles is known.
|
||||||
|
/// \param vertexCount Vertex count
|
||||||
|
/// \param triangleCount Triangle count
|
||||||
|
void reserve(size_t vertexCount, size_t triangleCount) { m_vertices.reserve(vertexCount); m_triangles.reserve(triangleCount); }
|
||||||
|
|
||||||
|
const QPainterPath& getBoundingPath() const { return m_boundingPath; }
|
||||||
|
void setBoundingPath(const QPainterPath& path) { m_boundingPath = path; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<QPointF> m_vertices;
|
||||||
|
std::vector<Triangle> m_triangles;
|
||||||
|
QPainterPath m_boundingPath;
|
||||||
|
};
|
||||||
|
|
||||||
/// Represents tiling/shading pattern
|
/// Represents tiling/shading pattern
|
||||||
class PDFPattern
|
class PDFPattern
|
||||||
{
|
{
|
||||||
@ -88,7 +169,7 @@ public:
|
|||||||
const PDFObject& patternGraphicState,
|
const PDFObject& patternGraphicState,
|
||||||
bool ignoreBackgroundColor);
|
bool ignoreBackgroundColor);
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
QRectF m_boundingBox;
|
QRectF m_boundingBox;
|
||||||
QMatrix m_matrix;
|
QMatrix m_matrix;
|
||||||
};
|
};
|
||||||
@ -102,6 +183,10 @@ public:
|
|||||||
virtual PatternType getType() const override;
|
virtual PatternType getType() const override;
|
||||||
virtual ShadingType getShadingType() const = 0;
|
virtual ShadingType getShadingType() const = 0;
|
||||||
|
|
||||||
|
/// Creates a colored mesh using settings
|
||||||
|
/// \param settings Meshing settings
|
||||||
|
virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings) const = 0;
|
||||||
|
|
||||||
/// Returns patterns graphic state. This state must be applied before
|
/// Returns patterns graphic state. This state must be applied before
|
||||||
/// the shading pattern is painted to the target device.
|
/// the shading pattern is painted to the target device.
|
||||||
const PDFObject& getPatternGraphicState() const { return m_patternGraphicState; }
|
const PDFObject& getPatternGraphicState() const { return m_patternGraphicState; }
|
||||||
@ -116,7 +201,7 @@ public:
|
|||||||
/// Returns true, if shading pattern should be anti-aliased
|
/// Returns true, if shading pattern should be anti-aliased
|
||||||
bool isAntialiasing() const { return m_antiAlias; }
|
bool isAntialiasing() const { return m_antiAlias; }
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
friend class PDFPattern;
|
friend class PDFPattern;
|
||||||
|
|
||||||
PDFObject m_patternGraphicState;
|
PDFObject m_patternGraphicState;
|
||||||
@ -130,7 +215,7 @@ class PDFSingleDimensionShading : public PDFShadingPattern
|
|||||||
public:
|
public:
|
||||||
explicit PDFSingleDimensionShading() = default;
|
explicit PDFSingleDimensionShading() = default;
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
friend class PDFPattern;
|
friend class PDFPattern;
|
||||||
|
|
||||||
std::vector<PDFFunctionPtr> m_functions;
|
std::vector<PDFFunctionPtr> m_functions;
|
||||||
@ -148,6 +233,7 @@ public:
|
|||||||
explicit PDFAxialShading() = default;
|
explicit PDFAxialShading() = default;
|
||||||
|
|
||||||
virtual ShadingType getShadingType() const override;
|
virtual ShadingType getShadingType() const override;
|
||||||
|
virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class PDFPattern;
|
friend class PDFPattern;
|
||||||
|
Reference in New Issue
Block a user