mirror of https://github.com/JakubMelka/PDF4QT.git
Function shading (first part)
This commit is contained in:
parent
320633d003
commit
06d08df83e
|
@ -312,14 +312,16 @@ QMatrix PDFDocumentDataLoaderDecorator::readMatrixFromDictionary(const PDFDictio
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
std::vector<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key)
|
||||
std::vector<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArrayFromDictionary(const PDFDictionary* dictionary,
|
||||
const char* key,
|
||||
std::vector<PDFReal> defaultValue)
|
||||
{
|
||||
if (dictionary->hasKey(key))
|
||||
{
|
||||
return readNumberArray(dictionary->get(key));
|
||||
return readNumberArray(dictionary->get(key), defaultValue);
|
||||
}
|
||||
|
||||
return std::vector<PDFReal>();
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
std::vector<PDFInteger> PDFDocumentDataLoaderDecorator::readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key)
|
||||
|
@ -382,7 +384,7 @@ std::vector<PDFObjectReference> PDFDocumentDataLoaderDecorator::readReferenceArr
|
|||
return std::vector<PDFObjectReference>();
|
||||
}
|
||||
|
||||
std::vector<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArray(const PDFObject& object) const
|
||||
std::vector<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArray(const PDFObject& object, std::vector<PDFReal> defaultValue) const
|
||||
{
|
||||
const PDFObject& dereferencedObject = m_document->getObject(object);
|
||||
if (dereferencedObject.isArray())
|
||||
|
@ -397,7 +399,7 @@ std::vector<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArray(const PDFOb
|
|||
const PDFReal number = readNumber(array->getItem(i), std::numeric_limits<PDFReal>::quiet_NaN());
|
||||
if (std::isnan(number))
|
||||
{
|
||||
return std::vector<PDFReal>();
|
||||
return defaultValue;
|
||||
}
|
||||
result.push_back(number);
|
||||
}
|
||||
|
@ -407,7 +409,7 @@ std::vector<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArray(const PDFOb
|
|||
return std::move(result);
|
||||
}
|
||||
|
||||
return std::vector<PDFReal>();
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
std::vector<PDFInteger> PDFDocumentDataLoaderDecorator::readIntegerArray(const PDFObject& object) const
|
||||
|
|
|
@ -201,8 +201,8 @@ public:
|
|||
QMatrix readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue);
|
||||
|
||||
/// Tries to read array of real values from dictionary. If entry dictionary doesn't exist,
|
||||
/// or error occurs, empty array is returned.
|
||||
std::vector<PDFReal> readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key);
|
||||
/// or error occurs, default value is returned.
|
||||
std::vector<PDFReal> readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, std::vector<PDFReal> defaultValue = std::vector<PDFReal>());
|
||||
|
||||
/// Tries to read array of integer values from dictionary. If entry dictionary doesn't exist,
|
||||
/// or error occurs, empty array is returned.
|
||||
|
@ -237,10 +237,10 @@ public:
|
|||
std::vector<PDFObjectReference> readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key);
|
||||
|
||||
/// Reads number array from dictionary. Reads all values. If some value is not
|
||||
/// real number (or integer number), empty array is returned. Empty array is also returned,
|
||||
/// real number (or integer number), default value is returned. Default value is also returned,
|
||||
/// if \p object is invalid.
|
||||
/// \param object Object containing array of numbers
|
||||
std::vector<PDFReal> readNumberArray(const PDFObject& object) const;
|
||||
std::vector<PDFReal> readNumberArray(const PDFObject& object, std::vector<PDFReal> defaultValue = std::vector<PDFReal>()) const;
|
||||
|
||||
/// Reads integer array from dictionary. Reads all values. If some value is not
|
||||
/// integer number, empty array is returned. Empty array is also returned,
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
|
||||
#include <QPainter>
|
||||
|
||||
#include <execution>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
|
@ -141,6 +143,43 @@ PDFPatternPtr PDFPattern::createShadingPattern(const PDFDictionary* colorSpaceDi
|
|||
const ShadingType shadingType = static_cast<ShadingType>(loader.readIntegerFromDictionary(shadingDictionary, "ShadingType", static_cast<PDFInteger>(ShadingType::Invalid)));
|
||||
switch (shadingType)
|
||||
{
|
||||
case ShadingType::Function:
|
||||
{
|
||||
PDFFunctionShading* functionShading = new PDFFunctionShading();
|
||||
PDFPatternPtr result(functionShading);
|
||||
|
||||
std::vector<PDFReal> functionDomain = loader.readNumberArrayFromDictionary(shadingDictionary, "Domain", { 0.0, 1.0, 0.0, 1.0 });
|
||||
if (functionDomain.size() != 4)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Invalid function shading pattern domain. Expected 4 values, but %1 provided.").arg(functionDomain.size()));
|
||||
}
|
||||
if (functionDomain[1] < functionDomain[0] || functionDomain[3] < functionDomain[2])
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Invalid function shading pattern domain. Invalid domain ranges."));
|
||||
}
|
||||
|
||||
QMatrix domainToTargetTransform = loader.readMatrixFromDictionary(shadingDictionary, "Matrix", QMatrix());
|
||||
|
||||
size_t colorComponentCount = colorSpace->getColorComponentCount();
|
||||
if (functions.size() > 1 && colorComponentCount != functions.size())
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Invalid axial shading pattern color functions. Expected %1 functions, but %2 provided.").arg(int(colorComponentCount)).arg(int(functions.size())));
|
||||
}
|
||||
|
||||
// Load items for function shading
|
||||
functionShading->m_antiAlias = antialias;
|
||||
functionShading->m_backgroundColor = backgroundColor;
|
||||
functionShading->m_colorSpace = colorSpace;
|
||||
functionShading->m_boundingBox = boundingBox;
|
||||
functionShading->m_domain = QRectF(functionDomain[0], functionDomain[2], functionDomain[1] - functionDomain[0], functionDomain[3] - functionDomain[2]);
|
||||
functionShading->m_domainToTargetTransform = domainToTargetTransform;
|
||||
functionShading->m_functions = qMove(functions);
|
||||
functionShading->m_matrix = matrix;
|
||||
functionShading->m_patternGraphicState = patternGraphicState;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case ShadingType::Axial:
|
||||
{
|
||||
PDFAxialShading* axialShading = new PDFAxialShading();
|
||||
|
@ -162,6 +201,12 @@ PDFPatternPtr PDFPattern::createShadingPattern(const PDFDictionary* colorSpaceDi
|
|||
throw PDFParserException(PDFTranslationContext::tr("Invalid axial shading pattern domain. Expected 2, but %1 provided.").arg(domain.size()));
|
||||
}
|
||||
|
||||
size_t colorComponentCount = colorSpace->getColorComponentCount();
|
||||
if (functions.size() > 1 && colorComponentCount != functions.size())
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Invalid axial shading pattern color functions. Expected %1 functions, but %2 provided.").arg(int(colorComponentCount)).arg(int(functions.size())));
|
||||
}
|
||||
|
||||
// Load items for axial shading
|
||||
axialShading->m_antiAlias = antialias;
|
||||
axialShading->m_backgroundColor = backgroundColor;
|
||||
|
@ -190,6 +235,244 @@ PDFPatternPtr PDFPattern::createShadingPattern(const PDFDictionary* colorSpaceDi
|
|||
return PDFPatternPtr();
|
||||
}
|
||||
|
||||
ShadingType PDFFunctionShading::getShadingType() const
|
||||
{
|
||||
return ShadingType::Function;
|
||||
}
|
||||
|
||||
PDFMesh PDFFunctionShading::createMesh(const PDFMeshQualitySettings& settings) const
|
||||
{
|
||||
PDFMesh mesh;
|
||||
|
||||
QMatrix domainToDeviceSpaceMatrix = m_domainToTargetTransform * settings.userSpaceToDeviceSpaceMatrix;
|
||||
QLineF topLine(m_domain.topLeft(), m_domain.topRight());
|
||||
QLineF leftLine(m_domain.topLeft(), m_domain.bottomLeft());
|
||||
|
||||
Q_ASSERT(domainToDeviceSpaceMatrix.isInvertible());
|
||||
QMatrix deviceSpaceToDomainMatrix = domainToDeviceSpaceMatrix.inverted();
|
||||
|
||||
QLineF topLineDS = domainToDeviceSpaceMatrix.map(topLine);
|
||||
QLineF leftLineDS = domainToDeviceSpaceMatrix.map(leftLine);
|
||||
|
||||
const size_t colorComponents = m_colorSpace->getColorComponentCount();
|
||||
const PDFReal resolution = settings.preferredMeshResolution;
|
||||
|
||||
const PDFReal xSteps = qMax(std::floor(topLineDS.length() / resolution), 2.0);
|
||||
const PDFReal ySteps = qMax(std::floor(leftLineDS.length() / resolution), 2.0);
|
||||
const PDFReal xStep = 1.0 / xSteps;
|
||||
const PDFReal yStep = 1.0 / ySteps;
|
||||
|
||||
// Prepare x/y ordinates array for given resolution
|
||||
std::vector<PDFReal> xOrdinates;
|
||||
std::vector<PDFReal> yOrdinates;
|
||||
xOrdinates.reserve(xSteps + 1);
|
||||
yOrdinates.reserve(ySteps + 1);
|
||||
|
||||
for (PDFReal x = 0.0; x <= 1.0; x += xStep)
|
||||
{
|
||||
xOrdinates.push_back(x);
|
||||
}
|
||||
if (xOrdinates.back() + PDF_EPSILON >= 1.0)
|
||||
{
|
||||
xOrdinates.pop_back();
|
||||
}
|
||||
xOrdinates.push_back(1.0);
|
||||
|
||||
for (PDFReal y = 0.0; y <= 1.0; y += yStep)
|
||||
{
|
||||
yOrdinates.push_back(y);
|
||||
}
|
||||
if (yOrdinates.back() + PDF_EPSILON >= 1.0)
|
||||
{
|
||||
yOrdinates.pop_back();
|
||||
}
|
||||
yOrdinates.push_back(1.0);
|
||||
|
||||
// We have determined x/y ordinates. Now we must create result array with colors,
|
||||
// which for each x/y ordinate tells us, what color in the given position is.
|
||||
const size_t rowCount = yOrdinates.size();
|
||||
const size_t columnCount = xOrdinates.size();
|
||||
const size_t nodesCount = rowCount * columnCount;
|
||||
const size_t stride = columnCount * colorComponents;
|
||||
|
||||
std::vector<size_t> indices;
|
||||
indices.resize(nodesCount, 0);
|
||||
std::iota(indices.begin(), indices.end(), 0);
|
||||
|
||||
auto indexToRowColumn = [columnCount](size_t index) -> std::pair<size_t, size_t>
|
||||
{
|
||||
return std::make_pair(index / columnCount, index % columnCount);
|
||||
};
|
||||
|
||||
auto rowColumnToIndex = [columnCount](size_t row, size_t column) -> size_t
|
||||
{
|
||||
return row * columnCount + column;
|
||||
};
|
||||
|
||||
auto rowColumnToFirstColorComponent = [stride, colorComponents](size_t row, size_t column) -> size_t
|
||||
{
|
||||
return row * stride + column * colorComponents;
|
||||
};
|
||||
|
||||
const bool isSingleFunction = m_functions.size() == 1;
|
||||
std::vector<PDFReal> sourceColorBuffer;
|
||||
sourceColorBuffer.resize(indices.size() * colorComponents, 0.0);
|
||||
|
||||
std::vector<QPointF> gridPoints;
|
||||
gridPoints.resize(nodesCount);
|
||||
|
||||
QMutex functionErrorMutex;
|
||||
PDFFunction::FunctionResult functionError(true);
|
||||
|
||||
auto setColor = [&](size_t index)
|
||||
{
|
||||
auto [row, column] = indexToRowColumn(index);
|
||||
QPointF nodeDS = topLineDS.pointAt(xOrdinates[column]) + leftLineDS.pointAt(yOrdinates[row]) - topLineDS.p1();
|
||||
QPointF node = deviceSpaceToDomainMatrix.map(nodeDS);
|
||||
const size_t colorComponentIndex = rowColumnToFirstColorComponent(row, column);
|
||||
Q_ASSERT(colorComponentIndex <= sourceColorBuffer.size());
|
||||
|
||||
gridPoints[index] = nodeDS;
|
||||
|
||||
PDFReal* sourceColorBegin = sourceColorBuffer.data() + colorComponentIndex;
|
||||
PDFReal* sourceColorEnd = sourceColorBegin + colorComponents;
|
||||
|
||||
std::array<PDFReal, 2> uv = { node.x(), node.y() };
|
||||
|
||||
if (isSingleFunction)
|
||||
{
|
||||
PDFFunction::FunctionResult result = m_functions.front()->apply(uv.data(), uv.data() + uv.size(), sourceColorBegin, sourceColorEnd);
|
||||
if (!result)
|
||||
{
|
||||
QMutexLocker lock(&functionErrorMutex);
|
||||
if (!functionError)
|
||||
{
|
||||
functionError = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0, count = colorComponents; i < count; ++i)
|
||||
{
|
||||
PDFFunction::FunctionResult result = m_functions[i]->apply(uv.data(), uv.data() + uv.size(), sourceColorBegin + i, sourceColorBegin + i + 1);
|
||||
if (!result)
|
||||
{
|
||||
QMutexLocker lock(&functionErrorMutex);
|
||||
if (!functionError)
|
||||
{
|
||||
functionError = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::for_each(std::execution::parallel_policy(), indices.cbegin(), indices.cend(), setColor);
|
||||
|
||||
if (!functionError)
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Error occured during mesh generation of shading: %1").arg(functionError.errorMessage));
|
||||
}
|
||||
|
||||
std::vector<QRgb> colors;
|
||||
colors.resize(rowCount * columnCount, QRgb());
|
||||
|
||||
mesh.setVertices(qMove(gridPoints));
|
||||
std::vector<PDFMesh::Triangle> triangles;
|
||||
triangles.resize((rowCount - 1) * (columnCount - 1) * 2);
|
||||
|
||||
auto generateTriangle = [&](size_t index)
|
||||
{
|
||||
auto [row, column] = indexToRowColumn(index);
|
||||
if (row == 0 || column == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(index == rowColumnToIndex(row, column));
|
||||
|
||||
const size_t triangleIndex1 = ((row - 1) * (columnCount - 1) + column - 1) * 2;
|
||||
const size_t triangleIndex2 = triangleIndex1 + 1;
|
||||
const size_t v1 = rowColumnToIndex(row - 1, column - 1);
|
||||
const size_t v2 = rowColumnToIndex(row - 1, column);
|
||||
const size_t v3 = index;
|
||||
const size_t v4 = rowColumnToIndex(row, column - 1);
|
||||
std::vector<PDFReal> colorBuffer;
|
||||
colorBuffer.resize(colorComponents, 0.0);
|
||||
|
||||
auto calculateColor = [&](const PDFMesh::Triangle& triangle)
|
||||
{
|
||||
QPointF centerDS = mesh.getTriangleCenter(triangle);
|
||||
QPointF center = deviceSpaceToDomainMatrix.map(centerDS);
|
||||
|
||||
std::array<PDFReal, 2> uv = { center.x(), center.y() };
|
||||
|
||||
if (isSingleFunction)
|
||||
{
|
||||
PDFFunction::FunctionResult result = m_functions.front()->apply(uv.data(), uv.data() + uv.size(), colorBuffer.data(), colorBuffer.data() + colorBuffer.size());
|
||||
if (!result)
|
||||
{
|
||||
QMutexLocker lock(&functionErrorMutex);
|
||||
if (!functionError)
|
||||
{
|
||||
functionError = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0, count = colorComponents; i < count; ++i)
|
||||
{
|
||||
PDFFunction::FunctionResult result = m_functions[i]->apply(uv.data(), uv.data() + uv.size(), colorBuffer.data() + i, colorBuffer.data() + i + 1);
|
||||
if (!result)
|
||||
{
|
||||
QMutexLocker lock(&functionErrorMutex);
|
||||
if (!functionError)
|
||||
{
|
||||
functionError = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_colorSpace->getColor(PDFAbstractColorSpace::convertToColor(colorBuffer));
|
||||
};
|
||||
|
||||
PDFMesh::Triangle triangle1;
|
||||
triangle1.v1 = static_cast<uint32_t>(v1);
|
||||
triangle1.v2 = static_cast<uint32_t>(v2);
|
||||
triangle1.v3 = static_cast<uint32_t>(v3);
|
||||
triangle1.color = calculateColor(triangle1).rgb();
|
||||
|
||||
PDFMesh::Triangle triangle2;
|
||||
triangle2.v1 = static_cast<uint32_t>(v3);
|
||||
triangle2.v2 = static_cast<uint32_t>(v4);
|
||||
triangle2.v3 = static_cast<uint32_t>(v1);
|
||||
triangle2.color = calculateColor(triangle2).rgb();
|
||||
|
||||
triangles[triangleIndex1] = triangle1;
|
||||
triangles[triangleIndex2] = triangle2;
|
||||
};
|
||||
std::for_each(std::execution::parallel_policy(), indices.cbegin(), indices.cend(), generateTriangle);
|
||||
mesh.setTriangles(qMove(triangles));
|
||||
|
||||
if (!functionError)
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Error occured during mesh generation of shading: %1").arg(functionError.errorMessage));
|
||||
}
|
||||
|
||||
// Create bounding path
|
||||
if (m_boundingBox.isValid())
|
||||
{
|
||||
QPainterPath boundingPath;
|
||||
boundingPath.addPolygon(settings.userSpaceToDeviceSpaceMatrix.map(m_boundingBox));
|
||||
mesh.setBoundingPath(boundingPath);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
PDFMesh PDFAxialShading::createMesh(const PDFMeshQualitySettings& settings) const
|
||||
{
|
||||
PDFMesh mesh;
|
||||
|
@ -271,7 +554,7 @@ PDFMesh PDFAxialShading::createMesh(const PDFMeshQualitySettings& settings) cons
|
|||
{
|
||||
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);
|
||||
PDFFunction::FunctionResult result = m_functions[i]->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));
|
||||
|
@ -460,6 +743,11 @@ void PDFMesh::transform(const QMatrix& matrix)
|
|||
m_boundingPath = matrix.map(m_boundingPath);
|
||||
}
|
||||
|
||||
QPointF PDFMesh::getTriangleCenter(const PDFMesh::Triangle& triangle) const
|
||||
{
|
||||
return (m_vertices[triangle.v1] + m_vertices[triangle.v2] + m_vertices[triangle.v3]) / 3.0;
|
||||
}
|
||||
|
||||
void PDFMeshQualitySettings::initDefaultResolution()
|
||||
{
|
||||
// We will take 0.5% percent of device space meshing area as minimal resolution (it is ~1.5 mm for
|
||||
|
|
|
@ -127,6 +127,17 @@ public:
|
|||
const QPainterPath& getBoundingPath() const { return m_boundingPath; }
|
||||
void setBoundingPath(const QPainterPath& path) { m_boundingPath = path; }
|
||||
|
||||
void setVertices(std::vector<QPointF>&& vertices) { m_vertices = qMove(vertices); }
|
||||
void setTriangles(std::vector<Triangle>&& triangles) { m_triangles = qMove(triangles); }
|
||||
|
||||
/// Returns vertex at given index
|
||||
/// \param index Index of the vertex
|
||||
const QPointF& getVertex(size_t index) const { return m_vertices[index]; }
|
||||
|
||||
/// Returns triangle center. Triangles vertice indices must be valid.
|
||||
/// \param triangle Triangle
|
||||
QPointF getTriangleCenter(const Triangle& triangle) const;
|
||||
|
||||
private:
|
||||
std::vector<QPointF> m_vertices;
|
||||
std::vector<Triangle> m_triangles;
|
||||
|
@ -188,7 +199,7 @@ public:
|
|||
|
||||
/// Creates a colored mesh using settings. Mesh is generated in device space
|
||||
/// coordinate system. You must transform the mesh, if you want to
|
||||
/// use it in another coordiante system.
|
||||
/// use it in another coordinate system.
|
||||
/// \param settings Meshing settings
|
||||
virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings) const = 0;
|
||||
|
||||
|
@ -232,6 +243,22 @@ protected:
|
|||
bool m_extendEnd = false;
|
||||
};
|
||||
|
||||
class PDFFunctionShading : public PDFShadingPattern
|
||||
{
|
||||
public:
|
||||
explicit PDFFunctionShading() = default;
|
||||
|
||||
virtual ShadingType getShadingType() const override;
|
||||
virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings) const override;
|
||||
|
||||
private:
|
||||
friend class PDFPattern;
|
||||
|
||||
QRectF m_domain; ///< Domain of the color function
|
||||
QMatrix m_domainToTargetTransform; ///< Transformation mapping from domain to shading coordinate space
|
||||
std::vector<PDFFunctionPtr> m_functions; ///< Color functions
|
||||
};
|
||||
|
||||
class PDFAxialShading : public PDFSingleDimensionShading
|
||||
{
|
||||
public:
|
||||
|
|
Loading…
Reference in New Issue