Basic support for transparency

This commit is contained in:
Jakub Melka 2019-09-29 15:44:35 +02:00
parent b09f9eff21
commit c1dfe0280c
10 changed files with 344 additions and 28 deletions

View File

@ -36,6 +36,7 @@ DEFINES += QT_DEPRECATED_WARNINGS
DESTDIR = $$OUT_PWD/..
SOURCES += \
sources/pdfblendfunction.cpp \
sources/pdfitemmodels.cpp \
sources/pdfobject.cpp \
sources/pdfoptionalcontent.cpp \
@ -64,6 +65,7 @@ SOURCES += \
sources/pdfimage.cpp
HEADERS += \
sources/pdfblendfunction.h \
sources/pdfitemmodels.h \
sources/pdfmeshqualitysettings.h \
sources/pdfobject.h \

View File

@ -0,0 +1,132 @@
// Copyright (C) 2019 Jakub Melka
//
// This file is part of PdfForQt.
//
// PdfForQt is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PdfForQt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
#include "pdfblendfunction.h"
namespace pdf
{
constexpr const std::pair<const char*, BlendMode> BLEND_MODE_INFOS[] =
{
{ "Normal", BlendMode::Normal },
{ "Multiply", BlendMode::Multiply },
{ "Screen", BlendMode::Screen },
{ "Overlay", BlendMode::Overlay },
{ "Darken", BlendMode::Darken },
{ "Lighten", BlendMode::Lighten },
{ "ColorDodge", BlendMode::ColorDodge },
{ "ColorBurn", BlendMode::ColorBurn },
{ "HardLight", BlendMode::HardLight },
{ "SoftLight", BlendMode::SoftLight },
{ "Difference", BlendMode::Difference },
{ "Exclusion", BlendMode::Exclusion },
{ "Hue", BlendMode::Hue },
{ "Saturation", BlendMode::Saturation },
{ "Color", BlendMode::Color },
{ "Luminosity", BlendMode::Luminosity },
{ "Compatible", BlendMode::Compatible }
};
BlendMode PDFBlendModeInfo::getBlendMode(const QByteArray& name)
{
for (const std::pair<const char*, BlendMode>& info : BLEND_MODE_INFOS)
{
if (info.first == name)
{
return info.second;
}
}
return BlendMode::Invalid;
}
bool PDFBlendModeInfo::isSupportedByQt(BlendMode mode)
{
switch (mode)
{
case BlendMode::Normal:
case BlendMode::Multiply:
case BlendMode::Screen:
case BlendMode::Overlay:
case BlendMode::Darken:
case BlendMode::Lighten:
case BlendMode::ColorDodge:
case BlendMode::ColorBurn:
case BlendMode::HardLight:
case BlendMode::SoftLight:
case BlendMode::Difference:
case BlendMode::Exclusion:
case BlendMode::Compatible:
return true;
default:
return false;
}
}
QPainter::CompositionMode PDFBlendModeInfo::getCompositionModeFromBlendMode(BlendMode mode)
{
switch (mode)
{
case BlendMode::Normal:
return QPainter::CompositionMode_SourceOver;
case BlendMode::Multiply:
return QPainter::CompositionMode_Multiply;
case BlendMode::Screen:
return QPainter::CompositionMode_Screen;
case BlendMode::Overlay:
return QPainter::CompositionMode_Overlay;
case BlendMode::Darken:
return QPainter::CompositionMode_Darken;
case BlendMode::Lighten:
return QPainter::CompositionMode_Lighten;
case BlendMode::ColorDodge:
return QPainter::CompositionMode_ColorDodge;
case BlendMode::ColorBurn:
return QPainter::CompositionMode_ColorBurn;
case BlendMode::HardLight:
return QPainter::CompositionMode_HardLight;
case BlendMode::SoftLight:
return QPainter::CompositionMode_SoftLight;
case BlendMode::Difference:
return QPainter::CompositionMode_Difference;
case BlendMode::Exclusion:
return QPainter::CompositionMode_Exclusion;
case BlendMode::Compatible:
return QPainter::CompositionMode_SourceOver;
default:
break;
}
return QPainter::CompositionMode_SourceOver;
}
QString PDFBlendModeInfo::getBlendModeName(BlendMode mode)
{
for (const std::pair<const char*, BlendMode>& info : BLEND_MODE_INFOS)
{
if (info.second == mode)
{
return QString::fromLatin1(info.first);
}
}
return "Unknown";
}
} // namespace pdf

View File

@ -0,0 +1,85 @@
// Copyright (C) 2019 Jakub Melka
//
// This file is part of PdfForQt.
//
// PdfForQt is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PdfForQt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
#ifndef PDFBLENDFUNCTION_H
#define PDFBLENDFUNCTION_H
#include "pdfglobal.h"
#include <QPainter>
namespace pdf
{
enum class BlendMode
{
// Separable blending modes
Normal, ///< Select source color, backdrop is ignored: B(source, dest) = source
Multiply, ///< Backdrop and source colors are multiplied: B(source, dest) = source * dest
Screen, ///< B(source, dest) = 1.0 - (1.0 - source) * (1.0 - dest) + source + dest - source * dest
Overlay, ///< B(source, dest) = HardLight(dest, source)
Darken, ///< Selects the darker of the colors. B(source, dest) = qMin(source, dest)
Lighten, ///< Selects the lighter of the colors. B(source, dest) = qMax(source, dest)
ColorDodge, ///< Brightens the backdrop color reflecting the source color. B(source, dest) = qMin(1.0, dest / (1.0 - source)), or 1.0, if source is 1.0
ColorBurn, ///< Darkens the backdrop color reflecting the source color. B(source, dest) = 1.0 - qMin(1.0, (1.0 - dest) / source), or 0.0, if source is 0.0
HardLight, ///< Multiply or screen the color, depending on the source value.
SoftLight, ///< Darkens or lightens the color, depending on the source value.
Difference, ///< Subtract the darker of colors from the lighter color. B(source, dest) = qAbs(source - dest)
Exclusion, ///< B(source, dest) = source + dest - 2 * source * dest
// Non-separable blending modes
Hue,
Saturation,
Color,
Luminosity,
// For compatibility - older PDF specification specifies Compatible mode, which is equal
// to normal. It should be recognized for sake of compatibility.
Compatible, ///< Equals to normal
// Invalid blending mode - for internal purposes only
Invalid
};
class PDFBlendModeInfo
{
public:
PDFBlendModeInfo() = delete;
/// Returns blend mode from the specified string. If string is invalid,
/// then \p Invalid blend mode is returned.
/// \param name Name of the blend mode
static BlendMode getBlendMode(const QByteArray& name);
/// Returns true, if blend mode is supported by Qt drawing subsystem
/// \param mode Blend mode
static bool isSupportedByQt(BlendMode mode);
/// Returns composition mode for Qt drawing subsystem from blend mode defined
/// in PDF standard. If blend mode is not supported by Qt drawing subsystem, then default
/// composition mode is returned.
/// \param mode Blend mode
static QPainter::CompositionMode getCompositionModeFromBlendMode(BlendMode mode);
/// Returns blend mode name
/// \param mode Blend mode
static QString getBlendModeName(BlendMode mode);
};
} // namespace pdf
#endif // PDFBLENDFUNCTION_H

View File

@ -43,7 +43,8 @@ enum RenderErrorType
{
Error,
Warning,
NotImplemented
NotImplemented,
NotSupported
};
struct PDFRenderError

View File

@ -1592,22 +1592,20 @@ void PDFPageContentProcessor::processApplyGraphicState(const PDFDictionary* grap
const PDFReal flatness = loader.readNumberFromDictionary(graphicStateDictionary, "FL", m_graphicState.getFlatness());
const PDFReal smoothness = loader.readNumberFromDictionary(graphicStateDictionary, "SM", m_graphicState.getSmoothness());
const bool textKnockout = loader.readBooleanFromDictionary(graphicStateDictionary, "TK", m_graphicState.getTextKnockout());
const PDFReal strokingAlpha = loader.readNumberFromDictionary(graphicStateDictionary, "CA", m_graphicState.getAlphaStroking());
const PDFReal fillingAlpha = loader.readNumberFromDictionary(graphicStateDictionary, "ca", m_graphicState.getAlphaFilling());
QByteArray blendModeName = loader.readNameFromDictionary(graphicStateDictionary, "BM");
// TODO: Implement alpha constant
const PDFReal strokingAlpha = loader.readNumberFromDictionary(graphicStateDictionary, "CA", 1.0);
const PDFReal fillingAlpha = loader.readNumberFromDictionary(graphicStateDictionary, "ca", 1.0);
QByteArray blendMode = loader.readNameFromDictionary(graphicStateDictionary, "BM");
if (strokingAlpha != 1.0)
if (!blendModeName.isEmpty())
{
m_errorList.append(PDFRenderError(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Alpha constant %1 for stroking not implemented!").arg(strokingAlpha)));
}
if (fillingAlpha != 1.0)
{
m_errorList.append(PDFRenderError(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Alpha constant %1 for filling not implemented!").arg(fillingAlpha)));
}
if (!blendMode.isEmpty() && blendMode != "Normal")
{
m_errorList.append(PDFRenderError(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Blend mode '%1' not implemented!").arg(QString::fromLatin1(blendMode))));
BlendMode blendMode = PDFBlendModeInfo::getBlendMode(blendModeName);
if (blendMode == BlendMode::Invalid)
{
blendMode = BlendMode::Normal;
m_errorList.append(PDFRenderError(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Blend mode '%1' is invalid.").arg(QString::fromLatin1(blendModeName))));
}
m_graphicState.setBlendMode(blendMode);
}
m_graphicState.setLineWidth(lineWidth);
@ -1617,6 +1615,8 @@ void PDFPageContentProcessor::processApplyGraphicState(const PDFDictionary* grap
m_graphicState.setFlatness(flatness);
m_graphicState.setSmoothness(smoothness);
m_graphicState.setTextKnockout(textKnockout);
m_graphicState.setAlphaStroking(strokingAlpha);
m_graphicState.setAlphaFilling(fillingAlpha);
updateGraphicState();
}
@ -2968,6 +2968,9 @@ PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorSt
m_textRenderingMode(TextRenderingMode::Fill),
m_textRise(0.0),
m_textKnockout(true),
m_alphaStroking(1.0),
m_alphaFilling(1.0),
m_blendMode(BlendMode::Normal),
m_stateFlags(StateUnchanged)
{
m_fillColorSpace.reset(new PDFDeviceGrayColorSpace);
@ -3005,6 +3008,9 @@ PDFPageContentProcessor::PDFPageContentProcessorState& PDFPageContentProcessor::
setTextKnockout(other.getTextKnockout());
setTextMatrix(other.getTextMatrix());
setTextLineMatrix(other.getTextLineMatrix());
setAlphaStroking(other.getAlphaStroking());
setAlphaFilling(other.getAlphaFilling());
setBlendMode(other.getBlendMode());
return *this;
}
@ -3161,6 +3167,47 @@ void PDFPageContentProcessor::PDFPageContentProcessorState::setTextLineMatrix(co
}
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setAlphaStroking(PDFReal alpha)
{
if (m_alphaStroking != alpha)
{
m_alphaStroking = alpha;
m_stateFlags |= StateAlphaStroking;
}
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setAlphaFilling(PDFReal alpha)
{
if (m_alphaFilling != alpha)
{
m_alphaFilling = alpha;
m_stateFlags |= StateAlphaFilling;
}
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setBlendMode(BlendMode mode)
{
if (m_blendMode != mode)
{
m_blendMode = mode;
m_stateFlags |= StateBlendMode;
}
}
QColor PDFPageContentProcessor::PDFPageContentProcessorState::getStrokeColorWithAlpha() const
{
QColor color = getStrokeColor();
color.setAlphaF(m_alphaStroking);
return color;
}
QColor PDFPageContentProcessor::PDFPageContentProcessorState::getFillColorWithAlpha() const
{
QColor color = getFillColor();
color.setAlphaF(m_alphaFilling);
return color;
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setTextMatrix(const QMatrix& textMatrix)
{
if (m_textMatrix != textMatrix)

View File

@ -24,6 +24,7 @@
#include "pdffont.h"
#include "pdfutils.h"
#include "pdfmeshqualitysettings.h"
#include "pdfblendfunction.h"
#include <QMatrix>
#include <QPainterPath>
@ -211,7 +212,7 @@ protected:
PDFPageContentProcessorState& operator=(PDFPageContentProcessorState&&) = delete;
PDFPageContentProcessorState& operator=(const PDFPageContentProcessorState& other);
enum StateFlag
enum StateFlag : uint32_t
{
StateUnchanged = 0x00000000,
StateCurrentTransformationMatrix = 0x00000001,
@ -238,7 +239,10 @@ protected:
StateTextRenderingMode = 0x00200000,
StateTextRise = 0x00400000,
StateTextKnockout = 0x00800000,
StateAll = 0xFFFF
StateAlphaStroking = 0x01000000,
StateAlphaFilling = 0x02000000,
StateBlendMode = 0x04000000,
StateAll = 0xFFFFFFFF
};
Q_DECLARE_FLAGS(StateFlags, StateFlag)
@ -318,6 +322,21 @@ protected:
const QMatrix& getTextLineMatrix() const { return m_textLineMatrix; }
void setTextLineMatrix(const QMatrix& textLineMatrix);
PDFReal getAlphaStroking() const { return m_alphaStroking; }
void setAlphaStroking(PDFReal alpha);
PDFReal getAlphaFilling() const { return m_alphaFilling; }
void setAlphaFilling(PDFReal alpha);
BlendMode getBlendMode() const { return m_blendMode; }
void setBlendMode(BlendMode mode);
/// Returns stroke color with alpha channel
QColor getStrokeColorWithAlpha() const;
/// Returns fill color with alpha channel
QColor getFillColorWithAlpha() const;
private:
QMatrix m_currentTransformationMatrix;
PDFColorSpacePointer m_strokeColorSpace;
@ -343,6 +362,9 @@ protected:
bool m_textKnockout;
QMatrix m_textMatrix;
QMatrix m_textLineMatrix;
PDFReal m_alphaStroking;
PDFReal m_alphaFilling;
BlendMode m_blendMode;
StateFlags m_stateFlags;
};

View File

@ -154,7 +154,7 @@ void PDFPainter::performMeshPainting(const PDFMesh& mesh)
{
m_painter->save();
m_painter->setWorldMatrix(QMatrix());
mesh.paint(m_painter);
mesh.paint(m_painter, getGraphicState()->getAlphaFilling());
m_painter->restore();
}
@ -173,16 +173,32 @@ void PDFPainter::performUpdateGraphicsState(const PDFPageContentProcessorState&
flags.testFlag(PDFPageContentProcessorState::StateLineCapStyle) ||
flags.testFlag(PDFPageContentProcessorState::StateLineJoinStyle) ||
flags.testFlag(PDFPageContentProcessorState::StateMitterLimit) ||
flags.testFlag(PDFPageContentProcessorState::StateLineDashPattern))
flags.testFlag(PDFPageContentProcessorState::StateLineDashPattern) ||
flags.testFlag(PDFPageContentProcessorState::StateAlphaStroking))
{
m_currentPen.dirty();
}
if (flags.testFlag(PDFPageContentProcessorState::StateFillColor))
if (flags.testFlag(PDFPageContentProcessorState::StateFillColor) ||
flags.testFlag(PDFPageContentProcessorState::StateAlphaFilling))
{
m_currentBrush.dirty();
}
// If current blend mode has changed, then update it
if (flags.testFlag(PDFPageContentProcessorState::StateBlendMode))
{
const BlendMode blendMode = state.getBlendMode();
const QPainter::CompositionMode compositionMode = PDFBlendModeInfo::getCompositionModeFromBlendMode(blendMode);
if (!PDFBlendModeInfo::isSupportedByQt(blendMode))
{
reportRenderError(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Blend mode '%1' not supported.").arg(PDFBlendModeInfo::getBlendModeName(blendMode)));
}
m_painter->setCompositionMode(compositionMode);
}
PDFPageContentProcessor::performUpdateGraphicsState(state);
}
@ -215,7 +231,7 @@ bool PDFPainter::isContentSuppressedByOC(PDFObjectReference ocgOrOcmd)
QPen PDFPainter::getCurrentPenImpl() const
{
const PDFPageContentProcessorState* graphicState = getGraphicState();
const QColor& color = graphicState->getStrokeColor();
const QColor& color = graphicState->getStrokeColorWithAlpha();
if (color.isValid())
{
const PDFReal lineWidth = graphicState->getLineWidth();
@ -253,7 +269,7 @@ QPen PDFPainter::getCurrentPenImpl() const
QBrush PDFPainter::getCurrentBrushImpl() const
{
const PDFPageContentProcessorState* graphicState = getGraphicState();
const QColor& color = graphicState->getFillColor();
const QColor& color = graphicState->getFillColorWithAlpha();
if (color.isValid())
{
return QBrush(color, Qt::SolidPattern);

View File

@ -1002,7 +1002,7 @@ PDFMesh PDFAxialShading::createMesh(const PDFMeshQualitySettings& settings) cons
return mesh;
}
void PDFMesh::paint(QPainter* painter) const
void PDFMesh::paint(QPainter* painter, PDFReal alpha) const
{
if (m_triangles.empty())
{
@ -1021,7 +1021,9 @@ void PDFMesh::paint(QPainter* painter) const
if (!m_backgroundPath.isEmpty() && m_backgroundColor.isValid())
{
painter->setBrush(QBrush(m_backgroundColor, Qt::SolidPattern));
QColor backgroundColor = m_backgroundColor;
backgroundColor.setAlphaF(alpha);
painter->setBrush(QBrush(backgroundColor, Qt::SolidPattern));
painter->drawPath(m_backgroundPath);
}
@ -1032,9 +1034,11 @@ void PDFMesh::paint(QPainter* painter) const
{
if (color != triangle.color)
{
painter->setPen(QColor(triangle.color));
painter->setBrush(QBrush(triangle.color, Qt::SolidPattern));
color = triangle.color;
QColor newColor(triangle.color);
newColor.setAlphaF(alpha);
painter->setPen(newColor);
painter->setBrush(QBrush(newColor, Qt::SolidPattern));
color = newColor;
}
std::array<QPointF, 3> triangleCorners = { m_vertices[triangle.v1], m_vertices[triangle.v2], m_vertices[triangle.v3] };

View File

@ -89,7 +89,8 @@ public:
/// Paints the mesh on the painter
/// \param painter Painter, onto which is mesh drawn
void paint(QPainter* painter) const;
/// \param alpha Opacity factor
void paint(QPainter* painter, PDFReal alpha) const;
/// Transforms the mesh according to the matrix transform
/// \param matrix Matrix transform to be performed

View File

@ -68,6 +68,12 @@ PDFRenderingErrorsWidget::PDFRenderingErrorsWidget(QWidget* parent, PDFWidget* p
break;
}
case RenderErrorType::NotSupported:
{
typeString = tr("Not supported");
break;
}
default:
{
Q_ASSERT(false);