diff --git a/Pdf4QtLib/sources/pdfpattern.cpp b/Pdf4QtLib/sources/pdfpattern.cpp index 7562e93..3b84ea4 100644 --- a/Pdf4QtLib/sources/pdfpattern.cpp +++ b/Pdf4QtLib/sources/pdfpattern.cpp @@ -2602,6 +2602,76 @@ QPointF PDFTensorPatch::getValue(PDFReal u, PDFReal v, int derivativeOrderU, int return result; } +bool PDFTensorPatch::getUV(PDFReal& u, PDFReal& v, PDFReal x, PDFReal y, PDFReal epsilon, int maximalNumberOfSteps) const +{ + int i = 0; + + // Jakub Melka: We are finding root of function F(u, v) defined as: + // + // F(u, v) = getValue(u, v) - (x, y) + // + // And using Newton-Raphson method to find the root + // v_n+1 = v_n - J^-1(v_n) * F(v_n) + // + // Where J^-1 is inverse of Jacobi matrix of the function F(u, v), defined as: + // dF1/du dF1/dv + // dF2/du dF2/dv + + QPointF v_n(u, v); + QPointF p_xy(x, y); + + while (i++ < maximalNumberOfSteps) + { + // Evaluate function at pivot + QPointF value_F_v_n = getValue(v_n.x(), v_n.y(), 0, 0) - p_xy; + + // Do we actually converge? + if (qAbs(value_F_v_n.x()) < epsilon && qAbs(value_F_v_n.y()) < epsilon) + { + u = v_n.x(); + v = v_n.y(); + + const bool uValid = u >= 0.0 && u <= 1.0; + const bool vValid = v >= 0.0 && v <= 1.0; + + return uValid && vValid; + } + + // Evaluate Jacobi matrix + QPointF dfdu = getValue(v_n.x(), v_n.y(), 1, 0); + QPointF dfdv = getValue(v_n.x(), v_n.y(), 0, 1); + + const PDFReal m11 = dfdu.x(); + const PDFReal m12 = dfdv.x(); + const PDFReal m21 = dfdu.y(); + const PDFReal m22 = dfdv.y(); + + // Create inverse of Jacobi matrix + const PDFReal determinant = m11 * m22 - m12 * m21; + + if (qFuzzyIsNull(determinant)) + { + // We did not converge, unfortunately, we are probably, + // in a stationary point. + return false; + } + + const PDFReal inverseDeterminant = 1.0 / determinant; + const PDFReal im11 = m22 * inverseDeterminant; + const PDFReal im12 = -m12 * inverseDeterminant; + const PDFReal im21 = -m21 * inverseDeterminant; + const PDFReal im22 = m11 * inverseDeterminant; + + QPointF imFirstRow(im11, im12); + QPointF imSecondRow(im21, im22); + QPointF delta(QPointF::dotProduct(imFirstRow, value_F_v_n), QPointF::dotProduct(imSecondRow, value_F_v_n)); + + v_n = v_n - delta; + } + + return false; +} + PDFReal PDFTensorPatch::getCurvature_u(PDFReal u, PDFReal v) const { QPointF dSdu = getDerivative_u(u, v); @@ -2751,11 +2821,9 @@ ShadingType PDFTensorProductPatchShading::getShadingType() const return ShadingType::TensorProductPatchMesh; } -PDFMesh PDFTensorProductPatchShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +PDFTensorPatches PDFTensorProductPatchShading::createPatches(QMatrix userSpaceToDeviceSpaceMatrix, bool transformColor) const { - PDFMesh mesh; - - QMatrix patternSpaceToDeviceSpaceMatrix = getPatternSpaceToDeviceSpaceMatrix(settings); + QMatrix patternSpaceToDeviceSpaceMatrix = getPatternSpaceToDeviceSpaceMatrix(userSpaceToDeviceSpaceMatrix); size_t bitsPerPatch = m_bitsPerFlag + 16 * 2 * m_bitsPerCoordinate + 4 * m_colorComponentCount * m_bitsPerComponent; size_t remainder = (8 - (bitsPerPatch % 8)) % 8; @@ -2785,7 +2853,7 @@ PDFMesh PDFTensorProductPatchShading::createMesh(const PDFMeshQualitySettings& s return patternSpaceToDeviceSpaceMatrix.map(QPointF(x, y)); }; - auto readColor = [this, &reader, colorScaleRatio]() -> PDFColor + auto readColor = [this, &reader, colorScaleRatio, transformColor]() -> PDFColor { PDFColor color; color.resize(m_colorComponentCount); @@ -2797,7 +2865,7 @@ PDFMesh PDFTensorProductPatchShading::createMesh(const PDFMeshQualitySettings& s color[i] = cMin + (reader.read(m_bitsPerComponent)) * (cMax - cMin) * colorScaleRatio; } - return getColor(color); + return transformColor ? getColor(color) : color; }; while (!reader.isAtEnd()) @@ -2966,11 +3034,26 @@ PDFMesh PDFTensorProductPatchShading::createMesh(const PDFMeshQualitySettings& s } default: - throw PDFException(PDFTranslationContext::tr("Invalid data in tensor product patch shading (flags = %1).").arg(flags)); + patches.clear(); + return patches; } } - fillMesh(mesh, patternSpaceToDeviceSpaceMatrix, settings, patches, cms, intent, reporter); + return patches; +} + +PDFMesh PDFTensorProductPatchShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + PDFMesh mesh; + + PDFTensorPatches patches = createPatches(settings.userSpaceToDeviceSpaceMatrix, true); + + if (patches.empty()) + { + throw PDFException(PDFTranslationContext::tr("Invalid data in tensor product patch shading.")); + } + + fillMesh(mesh, getPatternSpaceToDeviceSpaceMatrix(settings.userSpaceToDeviceSpaceMatrix), settings, patches, cms, intent, reporter); return mesh; } @@ -3015,6 +3098,99 @@ struct PDFTensorProductPatchShadingBase::Triangle } }; +class PDFTensorPatchesSample : public PDFShadingSampler +{ +public: + PDFTensorPatchesSample(const PDFTensorProductPatchShadingBase* shadingPattern, QMatrix userSpaceToDeviceSpaceMatrix) : + PDFShadingSampler(shadingPattern), + m_tensorProductShadingPattern(shadingPattern) + { + m_patches = shadingPattern->createPatches(userSpaceToDeviceSpaceMatrix, false); + std::reverse(m_patches.begin(), m_patches.end()); + } + + virtual bool sample(const QPointF& devicePoint, PDFColorBuffer outputBuffer, int limit) const override + { + constexpr PDFReal epsilon = 0.001; + std::array initialSamples = { QPointF(0.5, 0.5) }; + + for (const PDFTensorPatch& patch : m_patches) + { + PDFReal u = -1.0; + PDFReal v = -1.0; + + for (const QPointF& initialSample : initialSamples) + { + PDFReal uSample = initialSample.x(); + PDFReal vSample = initialSample.y(); + + if (patch.getUV(uSample, vSample, devicePoint.x(), devicePoint.y(), epsilon, limit)) + { + // We have successfully retrieved u,v source for the target x,y point. But is it actually + // better than previous sample? + if (vSample > v || (qAbs(vSample - v) < epsilon && uSample > u)) + { + u = uSample; + v = vSample; + } + } + } + + if (u >= 0.0 && v >= 0.0) + { + const PDFTensorPatch::Colors& colors = patch.getColors(); + const PDFColor& topLeft = colors[PDFTensorPatch::C_00]; + const PDFColor& topRight = colors[PDFTensorPatch::C_30]; + const PDFColor& bottomLeft = colors[PDFTensorPatch::C_03]; + const PDFColor& bottomRight = colors[PDFTensorPatch::C_33]; + + PDFColor color; + color.resize(topLeft.size()); + + const size_t colorComponentCount = color.size(); + for (size_t i = 0; i < colorComponentCount; ++i) + { + color[i] = topLeft[i] * (1.0 - u) * (1.0 - v) + + topRight[i] * u * (1.0 - v) + + bottomLeft[i] * (1.0 - u) * v + + bottomRight[i] * u * v; + } + + PDFColor finalColor = m_tensorProductShadingPattern->getColor(color); + if (finalColor.size() != outputBuffer.size()) + { + return false; + } + + for (size_t i = 0; i < finalColor.size(); ++i) + { + outputBuffer[i] = finalColor[i]; + } + + return true; + } + } + + return false; + } + +private: + const PDFTensorProductPatchShadingBase* m_tensorProductShadingPattern; + PDFTensorPatches m_patches; +}; + +PDFShadingSampler* PDFTensorProductPatchShadingBase::createSampler(QMatrix userSpaceToDeviceSpaceMatrix) const +{ + PDFTensorPatches patches = createPatches(userSpaceToDeviceSpaceMatrix, false); + + if (patches.empty()) + { + return nullptr; + } + + return new PDFTensorPatchesSample(this, userSpaceToDeviceSpaceMatrix); +} + void PDFTensorProductPatchShadingBase::fillMesh(PDFMesh& mesh, const PDFMeshQualitySettings& settings, const PDFTensorPatch& patch, @@ -3242,11 +3418,9 @@ ShadingType PDFCoonsPatchShading::getShadingType() const return ShadingType::CoonsPatchMesh; } -PDFMesh PDFCoonsPatchShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +PDFTensorPatches PDFCoonsPatchShading::createPatches(QMatrix userSpaceToDeviceSpaceMatrix, bool transformColor) const { - PDFMesh mesh; - - QMatrix patternSpaceToDeviceSpaceMatrix = getPatternSpaceToDeviceSpaceMatrix(settings); + QMatrix patternSpaceToDeviceSpaceMatrix = getPatternSpaceToDeviceSpaceMatrix(userSpaceToDeviceSpaceMatrix); size_t bitsPerPatch = m_bitsPerFlag + 16 * 2 * m_bitsPerCoordinate + 4 * m_colorComponentCount * m_bitsPerComponent; size_t remainder = (8 - (bitsPerPatch % 8)) % 8; @@ -3276,7 +3450,7 @@ PDFMesh PDFCoonsPatchShading::createMesh(const PDFMeshQualitySettings& settings, return patternSpaceToDeviceSpaceMatrix.map(QPointF(x, y)); }; - auto readColor = [this, &reader, colorScaleRatio]() -> PDFColor + auto readColor = [this, &reader, colorScaleRatio, transformColor]() -> PDFColor { PDFColor color; color.resize(m_colorComponentCount); @@ -3288,7 +3462,7 @@ PDFMesh PDFCoonsPatchShading::createMesh(const PDFMeshQualitySettings& settings, color[i] = cMin + (reader.read(m_bitsPerComponent)) * (cMax - cMin) * colorScaleRatio; } - return getColor(color); + return transformColor ? getColor(color) : color; }; std::array vertices; @@ -3432,11 +3606,26 @@ PDFMesh PDFCoonsPatchShading::createMesh(const PDFMeshQualitySettings& settings, } default: - throw PDFException(PDFTranslationContext::tr("Invalid data in coons patch shading (flags = %1).").arg(flags)); + // This is error, clear patches and return + patches.clear(); + return patches; } } - fillMesh(mesh, patternSpaceToDeviceSpaceMatrix, settings, patches, cms, intent, reporter); + return patches; +} + +PDFMesh PDFCoonsPatchShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + PDFMesh mesh; + PDFTensorPatches patches = createPatches(settings.userSpaceToDeviceSpaceMatrix, true); + + if (patches.empty()) + { + throw PDFException(PDFTranslationContext::tr("Invalid data in coons patch shading.")); + } + + fillMesh(mesh, getPatternSpaceToDeviceSpaceMatrix(settings), settings, patches, cms, intent, reporter); return mesh; } diff --git a/Pdf4QtLib/sources/pdfpattern.h b/Pdf4QtLib/sources/pdfpattern.h index 7d2644d..c73fda5 100644 --- a/Pdf4QtLib/sources/pdfpattern.h +++ b/Pdf4QtLib/sources/pdfpattern.h @@ -561,6 +561,18 @@ public: /// \param derivativeOrderV Derivation order in direction v (0 means no derivation) QPointF getValue(PDFReal u, PDFReal v, int derivativeOrderU, int derivativeOrderV) const; + /// Tries to find value (u,v) using Newton-Raphson numerical algorithm method. + /// This can fail and when it fails, false is returned. As initial point, input uv + /// is considered. Also, if maximal number of steps is reached, false is returned. + /// \param[in,out] u Output value of u (passed value is used as a seed) + /// \param[in,out] v Output value of v (passed value is used as a seed) + /// \param x Input x coordinate, for which we want to compute (u, v) + /// \param y Input y coordinate, for which we want to compute (u, v) + /// \param epsilon Epsilon, when distance from the getValue(u, v) and (x, y) + /// is less than epsilon, convergence is reached + /// \param maximalNumberOfSteps Maximal number of steps of Newton-Raphson algorithm + bool getUV(PDFReal& u, PDFReal& v, PDFReal x, PDFReal y, PDFReal epsilon, int maximalNumberOfSteps) const; + /// Calculates first derivate in the surface, in the direction of variable u. /// \param u Horizontal coordinate of the patch, must be in range [0, 1] /// \param v Vertical coordinate of the patch, must be in range [0, 1] @@ -653,6 +665,9 @@ class PDFTensorProductPatchShadingBase : public PDFType4567Shading public: explicit inline PDFTensorProductPatchShadingBase() = default; + virtual PDFShadingSampler* createSampler(QMatrix userSpaceToDeviceSpaceMatrix) const override; + virtual PDFTensorPatches createPatches(QMatrix userSpaceToDeviceSpaceMatrix, bool transformColor) const = 0; + protected: struct Triangle; @@ -671,6 +686,7 @@ public: virtual ShadingType getShadingType() const override; virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFTensorPatches createPatches(QMatrix userSpaceToDeviceSpaceMatrix, bool transformColor) const override; private: friend class PDFPattern; @@ -683,6 +699,7 @@ public: virtual ShadingType getShadingType() const override; virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFTensorPatches createPatches(QMatrix userSpaceToDeviceSpaceMatrix, bool transformColor) const override; private: friend class PDFPattern;