diff --git a/Pdf4QtLib/sources/pdfpattern.cpp b/Pdf4QtLib/sources/pdfpattern.cpp index 394f133..0c3bab9 100644 --- a/Pdf4QtLib/sources/pdfpattern.cpp +++ b/Pdf4QtLib/sources/pdfpattern.cpp @@ -45,6 +45,11 @@ QMatrix PDFShadingPattern::getPatternSpaceToDeviceSpaceMatrix(const PDFMeshQuali return m_matrix * settings.userSpaceToDeviceSpaceMatrix; } +QMatrix PDFShadingPattern::getPatternSpaceToDeviceSpaceMatrix(const QMatrix& userSpaceToDeviceSpaceMatrix) const +{ + return m_matrix * userSpaceToDeviceSpaceMatrix; +} + PDFShadingSampler* PDFShadingPattern::createSampler(QMatrix userSpaceToDeviceSpaceMatrix) const { Q_UNUSED(userSpaceToDeviceSpaceMatrix); @@ -1968,16 +1973,151 @@ PDFShadingSampler* PDFRadialShading::createSampler(QMatrix userSpaceToDeviceSpac return new PDFRadialShadingSampler(this, userSpaceToDeviceSpaceMatrix); } +class PDFTriangleShadingSampler : public PDFShadingSampler +{ +private: + struct Triangle + { + std::array vertexIndices = { }; + std::array vertexColors; + QMatrix barycentricCoordinateMatrix; + }; + +public: + PDFTriangleShadingSampler(const PDFType4567Shading* shadingPattern, QMatrix userSpaceToDeviceSpaceMatrix) : + PDFShadingSampler(shadingPattern), + m_type4567ShadingPattern(shadingPattern) + { + Q_UNUSED(userSpaceToDeviceSpaceMatrix); + } + + virtual bool sample(const QPointF& devicePoint, PDFColorBuffer outputBuffer, int limit) const override + { + Q_UNUSED(limit); + + for (const Triangle& triangle : m_triangles) + { + // Calculate barycentric coordinates + QPointF p3 = m_vertices[triangle.vertexIndices[2]]; + QPointF b1b2 = triangle.barycentricCoordinateMatrix.map(devicePoint - p3); + + const qreal b1 = b1b2.x(); + const qreal b2 = b1b2.y(); + const qreal b3 = 1.0 - b1 - b2; + + if (b1 >= 0.0 && b2 >= 0.0 && b3 >= 0.0 && qFuzzyCompare(b1 + b2 + b3, 1.0)) + { + // Jakub Melka: we got hit, we are in the triangle. Using the barycentric + // coordinates, we can calculate result color. + + const PDFColor& c1 = triangle.vertexColors[0]; + const PDFColor& c2 = triangle.vertexColors[1]; + const PDFColor& c3 = triangle.vertexColors[2]; + + Q_ASSERT(c1.size() == c2.size()); + Q_ASSERT(c2.size() == c3.size()); + + const size_t inputColorSize = c1.size(); + PDFColor interpolatedColor; + interpolatedColor.resize(inputColorSize); + for (size_t i = 0; i < inputColorSize; ++i) + { + interpolatedColor[i] = c1[i] * b1 + c2[i] * b2 + c3[i] * b3; + } + + interpolatedColor = m_type4567ShadingPattern->getColor(interpolatedColor); + + if (interpolatedColor.size() != outputBuffer.size()) + { + return false; + } + + for (size_t i = 0; i < outputBuffer.size(); ++i) + { + outputBuffer[i] = interpolatedColor[i]; + } + + return true; + } + } + + return false; + } + + void addTriangle(std::array vertexIndices, std::array vertexColors) + { + Triangle triangle; + triangle.vertexIndices = qMove(vertexIndices); + triangle.vertexColors = qMove(vertexColors); + + // Compute barycentric coordinate matrix, which will tranform cartesian coordinates of given + // point in the plane into the barycentric coordinates in the triangle. Barycentric coordinate system + // is three point coordinates (b1, b2, b3), where b1,b2,b3 >= 0 and b1 + b2 + b3 = 1.0, such that + // + // (x, y) = b1 * p1 + b2 * p2 + b3 * p3, where + // triangle consists of vertices p1, p2, p3 and (x, y) is point inside triangle. If requirements + // of b1, b2, b3 are not met, then point doesn't lie in the triangle. + // + // We will use following transformation from caresian plane to barycentric coordinate system: + // Usign equation b1 + b2 + b3 = 1.0 we get b3 = 1.0 - b1 - b2, so we will get following system + // of equations: + // + // x = b1 * x1 + b2 * x2 + (1.0 - b1 - b2) * x3 + // y = b1 * y1 + b2 * y2 + (1.0 - b1 - b2) * y3 + // + // b1 * (x1 - x3) + b2 * (x2 - x3) = x - x3 + // b1 * (y1 - y3) + b2 * (y2 - y3) = y - y3 + // + // Now, we have system of two linear equation of two variables (b1, b2) and b3 can be computed + // easily from equation b1 + b2 + b3 = 1.0. Now, we will introduce matrix B: + // + // B = ( x1 - x3, x2 - x3) + // ( y1 - y3, y2 - y3) + // + // And we will have final equation: + // + // (b1, b2) = B^-1 * (p - p3) + // + + QPointF p1 = m_vertices[triangle.vertexIndices[0]]; + QPointF p2 = m_vertices[triangle.vertexIndices[1]]; + QPointF p3 = m_vertices[triangle.vertexIndices[2]]; + + QPointF p1p3 = p1 - p3; + QPointF p2p3 = p2 - p3; + + QMatrix B(p1p3.x(), p2p3.x(), p1p3.y(), p2p3.y(), 0.0, 0.0); + + if (!B.isInvertible()) + { + // Jakub Melka: B is is not invertible, triangle is degenerated + return; + } + + triangle.barycentricCoordinateMatrix = B.inverted(); + m_triangles.emplace_back(qMove(triangle)); + } + + void setVertexArray(std::vector&& vertices) { m_vertices = qMove(vertices); } + void reserveSpaceForTriangles(size_t triangleCount) { m_triangles.reserve(triangleCount); } + +private: + const PDFType4567Shading* m_type4567ShadingPattern; + std::vector m_vertices; + std::vector m_triangles; +}; + ShadingType PDFFreeFormGouradTriangleShading::getShadingType() const { return ShadingType::FreeFormGouradTriangle; } -PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +bool PDFFreeFormGouradTriangleShading::processTriangles(InitializeFunction initializeMeshFunction, + AddTriangleFunction addTriangle, + const QMatrix& userSpaceToDeviceSpaceMatrix, + bool convertColors) const { - PDFMesh mesh; - - QMatrix patternSpaceToDeviceSpaceMatrix = getPatternSpaceToDeviceSpaceMatrix(settings); + QMatrix patternSpaceToDeviceSpaceMatrix = getPatternSpaceToDeviceSpaceMatrix(userSpaceToDeviceSpaceMatrix); size_t bitsPerVertex = m_bitsPerFlag + 2 * m_bitsPerCoordinate + m_colorComponentCount * m_bitsPerComponent; size_t remainder = (8 - (bitsPerVertex % 8)) % 8; bitsPerVertex += remainder; @@ -1987,21 +2127,12 @@ PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySetting if (vertexCount < 3) { // No mesh produced - return mesh; + return true; } // We have 3 vertices for start triangle, then for each new vertex, we get // a new triangle, or, based on flags, no triangle (if new triangle is processed) size_t triangleCount = vertexCount - 2; - mesh.reserve(0, triangleCount); - - struct VertexData - { - uint32_t index = 0; - uint8_t flags = 0; - QPointF position; - PDFColor color; - }; const PDFReal vertexScaleRatio = 1.0 / double((static_cast(1) << m_bitsPerCoordinate) - 1); const PDFReal xScaleRatio = (m_xmax - m_xmin) * vertexScaleRatio; @@ -2013,7 +2144,7 @@ PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySetting std::vector meshVertices; meshVertices.resize(vertexCount); - auto readVertex = [this, &vertices, &patternSpaceToDeviceSpaceMatrix, &meshVertices, bytesPerVertex, xScaleRatio, yScaleRatio, colorScaleRatio](size_t index) + auto readVertex = [this, &vertices, &patternSpaceToDeviceSpaceMatrix, &meshVertices, bytesPerVertex, xScaleRatio, yScaleRatio, colorScaleRatio, convertColors](size_t index) { PDFBitReader reader(&m_data, 8); reader.seek(index * bytesPerVertex); @@ -2034,14 +2165,17 @@ PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySetting data.color[i] = cMin + (reader.read(m_bitsPerComponent)) * (cMax - cMin) * colorScaleRatio; } - data.color = getColor(data.color); + if (convertColors) + { + data.color = getColor(data.color); + } vertices[index] = qMove(data); }; PDFIntegerRange indices(size_t(0), vertexCount); PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, indices.begin(), indices.end(), readVertex); - mesh.setVertices(qMove(meshVertices)); + initializeMeshFunction(qMove(meshVertices), triangleCount); vertices.front().flags = 0; @@ -2050,15 +2184,6 @@ PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySetting const VertexData* vc = nullptr; const VertexData* vd = nullptr; - auto addTriangle = [this, &settings, &mesh, cms, intent, reporter](const VertexData* va, const VertexData* vb, const VertexData* vc) - { - const uint32_t via = va->index; - const uint32_t vib = vb->index; - const uint32_t vic = vc->index; - - addSubdividedTriangles(settings, mesh, via, vib, vic, va->color, vb->color, vc->color, cms, intent, reporter); - }; - for (size_t i = 0; i < vertexCount;) { vd = &vertices[i]; @@ -2069,7 +2194,7 @@ PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySetting { if (i + 2 >= vertexCount) { - throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid free form gourad triangle data stream - not enough vertices.")); + return false; } va = vd; vb = &vertices[i + 1]; @@ -2101,11 +2226,37 @@ PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySetting } default: - throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid free form gourad triangle data stream - invalid vertex flag %1.").arg(vd->flags)); - break; + return false; } } + return true; +} + +PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + PDFMesh mesh; + + auto addTriangle = [this, &settings, &mesh, cms, intent, reporter](const VertexData* va, const VertexData* vb, const VertexData* vc) + { + const uint32_t via = va->index; + const uint32_t vib = vb->index; + const uint32_t vic = vc->index; + + addSubdividedTriangles(settings, mesh, via, vib, vic, va->color, vb->color, vc->color, cms, intent, reporter); + }; + + auto initializeMeshFunction = [&mesh](std::vector&& vertices, size_t triangleCount) + { + mesh.reserve(0, triangleCount); + mesh.setVertices(qMove(vertices)); + }; + + if (!processTriangles(initializeMeshFunction, addTriangle, settings.userSpaceToDeviceSpaceMatrix, true)) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid free form gourad triangle data stream.")); + } + if (m_backgroundColor.isValid()) { QPainterPath path; @@ -2117,6 +2268,35 @@ PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySetting return mesh; } +PDFShadingSampler* PDFFreeFormGouradTriangleShading::createSampler(QMatrix userSpaceToDeviceSpaceMatrix) const +{ + PDFTriangleShadingSampler* sampler = new PDFTriangleShadingSampler(this, userSpaceToDeviceSpaceMatrix); + + auto addTriangle = [sampler](const VertexData* va, const VertexData* vb, const VertexData* vc) + { + const uint32_t via = va->index; + const uint32_t vib = vb->index; + const uint32_t vic = vc->index; + + sampler->addTriangle({ via, vib, vic }, { va->color, vb->color, vc->color }); + }; + + auto initializeMeshFunction = [sampler](std::vector&& vertices, size_t triangleCount) + { + sampler->setVertexArray(qMove(vertices)); + sampler->reserveSpaceForTriangles(triangleCount); + }; + + if (!processTriangles(initializeMeshFunction, addTriangle, userSpaceToDeviceSpaceMatrix, false)) + { + // Just delete the sampler, data are invalid + delete sampler; + sampler = nullptr; + } + + return sampler; +} + ShadingType PDFLatticeFormGouradTriangleShading::getShadingType() const { return ShadingType::LatticeFormGouradTriangle; diff --git a/Pdf4QtLib/sources/pdfpattern.h b/Pdf4QtLib/sources/pdfpattern.h index 88a1a27..eb3273e 100644 --- a/Pdf4QtLib/sources/pdfpattern.h +++ b/Pdf4QtLib/sources/pdfpattern.h @@ -335,6 +335,9 @@ public: /// Returns matrix transforming pattern space to device space QMatrix getPatternSpaceToDeviceSpaceMatrix(const PDFMeshQualitySettings& settings) const; + /// Returns matrix transforming pattern space to device space + QMatrix getPatternSpaceToDeviceSpaceMatrix(const QMatrix& userSpaceToDeviceSpaceMatrix) const; + /// Create sampler which can compute shading colors in device space coordinates. If sampler can't /// be created (or shading is invalid), then nullptr is returned. /// \param userSpaceToDeviceSpaceMatrix Matrix, which transforms user space points @@ -434,12 +437,12 @@ class PDFType4567Shading : public PDFShadingPattern public: explicit PDFType4567Shading() = default; -protected: - friend class PDFPattern; - /// Returns color for given color or function parameter PDFColor getColor(PDFColor colorOrFunctionParameter) const; +protected: + friend class PDFPattern; + void addSubdividedTriangles(const PDFMeshQualitySettings& settings, PDFMesh& mesh, uint32_t v1, @@ -477,9 +480,26 @@ public: virtual ShadingType getShadingType() const override; virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFShadingSampler* createSampler(QMatrix userSpaceToDeviceSpaceMatrix) const override; private: + struct VertexData + { + uint32_t index = 0; + uint8_t flags = 0; + QPointF position; + PDFColor color; + }; + friend class PDFPattern; + + using InitializeFunction = std::function&&, size_t)>; + using AddTriangleFunction = std::function; + + bool processTriangles(InitializeFunction initializeMeshFunction, + AddTriangleFunction addTriangle, + const QMatrix& userSpaceToDeviceSpaceMatrix, + bool convertColors) const; }; class PDFLatticeFormGouradTriangleShading : public PDFType4567Shading diff --git a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp index e8f88dc..9cc7429 100644 --- a/Pdf4QtLib/sources/pdftransparencyrenderer.cpp +++ b/Pdf4QtLib/sources/pdftransparencyrenderer.cpp @@ -53,12 +53,18 @@ PDFFloatBitmap::PDFFloatBitmap(size_t width, size_t height, PDFPixelFormat forma PDFColorBuffer PDFFloatBitmap::getPixel(size_t x, size_t y) { + Q_ASSERT(x < m_width); + Q_ASSERT(y < m_height); + const size_t index = getPixelIndex(x, y); return PDFColorBuffer(m_data.data() + index, m_pixelSize); } PDFConstColorBuffer PDFFloatBitmap::getPixel(size_t x, size_t y) const { + Q_ASSERT(x < m_width); + Q_ASSERT(y < m_height); + const size_t index = getPixelIndex(x, y); return PDFConstColorBuffer(m_data.data() + index, m_pixelSize); } @@ -1912,7 +1918,10 @@ bool PDFTransparencyRenderer::performPathPaintingUsingShading(const QPainterPath { for (int y = fillRect.top(); y <= fillRect.bottom(); ++y) { - PDFColorBuffer buffer = texture.getPixel(x, y); + const int texelCoordinateX = x - fillRect.left(); + const int texelCoordinateY = y - fillRect.top(); + + PDFColorBuffer buffer = texture.getPixel(texelCoordinateX, texelCoordinateY); bool isSampled = sampler->sample(QPointF(x, y) + offset, buffer.resized(shadingColorComponentCount), m_settings.shadingAlgorithmLimit); const PDFColorComponent textureSampleShape = isSampled ? 1.0f : 0.0f; buffer[textureShapeChannel] = textureSampleShape; @@ -1929,7 +1938,10 @@ bool PDFTransparencyRenderer::performPathPaintingUsingShading(const QPainterPath { for (int x = fillRect.left(); x <= fillRect.right(); ++x) { - PDFColorBuffer buffer = texture.getPixel(x, y); + const int texelCoordinateX = x - fillRect.left(); + const int texelCoordinateY = y - fillRect.top(); + + PDFColorBuffer buffer = texture.getPixel(texelCoordinateX, texelCoordinateY); bool isSampled = sampler->sample(QPointF(x, y) + offset, buffer.resized(shadingColorComponentCount), m_settings.shadingAlgorithmLimit); const PDFColorComponent textureSampleShape = isSampled ? 1.0f : 0.0f; buffer[textureShapeChannel] = textureSampleShape;