Issue #108: Add accessibility options in pdf reader

This commit is contained in:
Jakub Melka 2023-11-18 18:52:18 +01:00
parent 2408c61b5f
commit 6f6ddaab04
23 changed files with 600 additions and 89 deletions

View File

@ -102,6 +102,8 @@ add_library(Pdf4QtLib SHARED
sources/pdfdocumentsanitizer.cpp
sources/pdfimageconversion.h
sources/pdfimageconversion.cpp
sources/pdfcolorconvertor.h
sources/pdfcolorconvertor.cpp
cmaps.qrc
)

View File

@ -851,7 +851,8 @@ void PDFCreateStampTool::drawPage(QPainter* painter,
parameters.painter = painter;
parameters.annotation = const_cast<PDFStampAnnotation*>(&m_stampAnnotation);
parameters.key.first = PDFAppeareanceStreams::Appearance::Normal;
parameters.invertColors = getProxy()->getFeatures().testFlag(PDFRenderer::InvertColors);
parameters.colorConvertor = getProxy()->getCMSManager()->getColorConvertor();
PDFRenderer::applyFeaturesToColorConvertor(getProxy()->getFeatures(), parameters.colorConvertor);
m_stampAnnotation.draw(parameters);
}

View File

@ -1474,7 +1474,9 @@ void PDFAnnotationManager::drawAnnotationDirect(const PageAnnotation& annotation
parameters.annotation = annotation.annotation.data();
parameters.formManager = m_formManager;
parameters.key = std::make_pair(annotation.appearance, annotation.annotation->getAppearanceState());
parameters.invertColors = m_features.testFlag(PDFRenderer::InvertColors);
parameters.colorConvertor = cms->getColorConvertor();
PDFRenderer::applyFeaturesToColorConvertor(m_features, parameters.colorConvertor);
annotation.annotation->draw(parameters);
if (parameters.boundingRectangle.isValid())

View File

@ -29,6 +29,7 @@
#include "pdfrenderer.h"
#include "pdfblendfunction.h"
#include "pdfdocument.h"
#include "pdfcolorconvertor.h"
#include <QCursor>
#include <QPainterPath>
@ -474,8 +475,8 @@ struct AnnotationDrawParameters
/// Appeareance mode (normal/rollover/down, and appearance state)
PDFAppeareanceStreams::Key key;
/// Invert colors?
bool invertColors = false;
/// Color convertor
PDFColorConvertor colorConvertor;
};
/// Base class for all annotation types. Represents PDF annotation object.

View File

@ -64,7 +64,9 @@ namespace pdf
class PDFLittleCMS : public PDFCMS
{
public:
explicit PDFLittleCMS(const PDFCMSManager* manager, const PDFCMSSettings& settings);
explicit PDFLittleCMS(const PDFCMSManager* manager,
const PDFCMSSettings& settings,
const PDFColorConvertor& colorConvertor);
virtual ~PDFLittleCMS() override;
virtual bool isCompatible(const PDFCMSSettings& settings) const override;
@ -80,6 +82,7 @@ public:
virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override;
virtual bool fillRGBBufferFromICC(const std::vector<float>& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override;
virtual bool transformColorSpace(const ColorSpaceTransformParams& params) const override;
virtual PDFColorConvertor getColorConvertor() const override;
private:
void init();
@ -162,6 +165,7 @@ private:
PDFCMSSettings m_settings;
QColor m_paperColor;
std::array<cmsHPROFILE, ProfileCount> m_profiles;
PDFColorConvertor m_colorConvertor;
mutable QReadWriteLock m_transformationCacheLock;
mutable std::unordered_map<int, cmsHTRANSFORM> m_transformationCache;
@ -445,11 +449,19 @@ bool PDFLittleCMS::transformColorSpace(const PDFCMS::ColorSpaceTransformParams&
return false;
}
PDFLittleCMS::PDFLittleCMS(const PDFCMSManager* manager, const PDFCMSSettings& settings) :
PDFColorConvertor PDFLittleCMS::getColorConvertor() const
{
return m_colorConvertor;
}
PDFLittleCMS::PDFLittleCMS(const PDFCMSManager* manager,
const PDFCMSSettings& settings,
const PDFColorConvertor& colorConvertor) :
m_manager(manager),
m_settings(settings),
m_paperColor(Qt::white),
m_profiles()
m_profiles(),
m_colorConvertor(colorConvertor)
{
static const int installed = installCmsPlugins();
Q_UNUSED(installed);
@ -1286,6 +1298,12 @@ QString getInfoFromProfile(cmsHPROFILE profile, cmsInfoType infoType)
return QString();
}
PDFCMSGeneric::PDFCMSGeneric(const PDFColorConvertor& colorConvertor) :
m_colorConvertor(colorConvertor)
{
}
bool PDFCMSGeneric::isCompatible(const PDFCMSSettings& settings) const
{
return settings.system == PDFCMSSettings::System::Generic;
@ -1397,6 +1415,11 @@ bool PDFCMSGeneric::transformColorSpace(const PDFCMS::ColorSpaceTransformParams&
return false;
}
PDFColorConvertor PDFCMSGeneric::getColorConvertor() const
{
return m_colorConvertor;
}
PDFCMSManager::PDFCMSManager(QObject* parent) :
BaseClass(parent),
m_document(nullptr),
@ -1432,6 +1455,11 @@ void PDFCMSManager::setSettings(const PDFCMSSettings& settings)
}
}
PDFColorConvertor PDFCMSManager::getColorConvertor() const
{
return PDFColorConvertor();
}
const PDFColorProfileIdentifiers& PDFCMSManager::getOutputProfiles() const
{
QMutexLocker lock(&m_mutex);
@ -1606,10 +1634,10 @@ PDFCMSPointer PDFCMSManager::getCurrentCMSImpl() const
switch (m_settings.system)
{
case PDFCMSSettings::System::Generic:
return PDFCMSPointer(new PDFCMSGeneric());
return PDFCMSPointer(new PDFCMSGeneric(getColorConvertor()));
case PDFCMSSettings::System::LittleCMS2:
return PDFCMSPointer(new PDFLittleCMS(this, m_settings));
return PDFCMSPointer(new PDFLittleCMS(this, m_settings, getColorConvertor()));
default:
Q_ASSERT(false);

View File

@ -22,6 +22,7 @@
#include "pdfcolorspaces.h"
#include "pdfexception.h"
#include "pdfutils.h"
#include "pdfcolorconvertor.h"
#include <QRecursiveMutex>
#include <QSharedPointer>
@ -211,6 +212,13 @@ public:
const QByteArray& iccData,
PDFRenderErrorReporter* reporter) const = 0;
/// Returns color convertor of post-processing colors
/// produced by color management system. Color convertor
/// does not have set color conversion mode, it must be
/// set manually.
/// \return Color convertor according to current settings.
virtual PDFColorConvertor getColorConvertor() const = 0;
enum ColorSpaceType
{
Invalid,
@ -254,6 +262,7 @@ class PDF4QTLIBSHARED_EXPORT PDFCMSGeneric : public PDFCMS
{
public:
explicit inline PDFCMSGeneric() = default;
explicit inline PDFCMSGeneric(const PDFColorConvertor& colorConvertor);
virtual bool isCompatible(const PDFCMSSettings& settings) const override;
virtual QColor getPaperColor() const override;
@ -268,6 +277,10 @@ public:
virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector<float>& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override;
virtual bool fillRGBBufferFromICC(const std::vector<float>& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override;
virtual bool transformColorSpace(const ColorSpaceTransformParams& params) const override;
virtual PDFColorConvertor getColorConvertor() const override;
private:
PDFColorConvertor m_colorConvertor;
};
struct PDFColorProfileIdentifier
@ -359,6 +372,13 @@ public:
const PDFCMSSettings& getSettings() const { return m_settings; }
void setSettings(const PDFCMSSettings& settings);
/// Returns color convertor of post-processing colors
/// produced by color management system. Color convertor
/// does not have set color conversion mode, it must be
/// set manually.
/// \return Color convertor according to current settings.
PDFColorConvertor getColorConvertor() const;
const PDFColorProfileIdentifiers& getOutputProfiles() const;
const PDFColorProfileIdentifiers& getGrayProfiles() const;
const PDFColorProfileIdentifiers& getRGBProfiles() const;

View File

@ -0,0 +1,210 @@
// Copyright (C) 2023 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT 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
// with the written consent of the copyright owner, any later version.
//
// PDF4QT 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 PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#include "pdfcolorconvertor.h"
#include "pdfimageconversion.h"
#include "pdfutils.h"
namespace pdf
{
PDFColorConvertor::PDFColorConvertor()
{
calculateSigmoidParams();
}
bool PDFColorConvertor::isActive() const
{
return m_mode != Mode::Normal;
}
void PDFColorConvertor::setMode(Mode mode)
{
m_mode = mode;
}
QColor PDFColorConvertor::convert(QColor color, bool background, bool foreground) const
{
switch (m_mode)
{
case Mode::Normal:
return color;
case Mode::InvertedColors:
return invertColor(color);
case Mode::Grayscale:
{
int gray = qGray(color.red(), color.green(), color.blue());
return QColor(gray, gray, gray, color.alpha());
}
case Mode::HighContrast:
{
const float lightness = color.lightnessF();
const float adjustedLightness = correctLigthnessBySigmoidFunction(lightness);
QColor hslColor = color.toHsl();
hslColor.setHslF(hslColor.hueF(), hslColor.saturationF(), adjustedLightness, color.alphaF());
return hslColor.toRgb();
}
case Mode::Bitonal:
{
const int lightness = color.lightness();
QColor bitonalColor = (lightness >= m_bitonalThreshold) ? QColor(Qt::white) : QColor(Qt::black);
bitonalColor.setAlpha(bitonalColor.alpha());
return bitonalColor;
}
case Mode::CustomColors:
{
if (background)
{
return m_backgroundColor;
}
if (foreground)
{
return m_foregroundColor;
}
const float lightness = color.lightnessF();
QColor hslColor = m_foregroundColor;
hslColor.setHslF(hslColor.hueF(), hslColor.saturationF(), lightness, color.alphaF());
return hslColor.toRgb();
}
default:
Q_ASSERT(false);
break;
}
return color;
}
QImage PDFColorConvertor::convert(QImage image) const
{
switch (m_mode)
{
case Mode::Normal:
return image;
case Mode::InvertedColors:
{
image.invertPixels(QImage::InvertRgb);
return image;
}
case Mode::Grayscale:
{
QImage alpha = image.convertedTo(QImage::Format_Alpha8);
QImage grayscaleImage = image.convertedTo(QImage::Format_Grayscale8);
QImage resultImage = grayscaleImage;
resultImage = resultImage.convertedTo(QImage::Format_ARGB32);
resultImage.setAlphaChannel(std::move(alpha));
return resultImage;
}
case Mode::Bitonal:
{
PDFImageConversion imageConversion;
imageConversion.setConversionMethod(PDFImageConversion::ConversionMethod::Automatic);
imageConversion.setImage(image);
if (imageConversion.convert())
{
return imageConversion.getConvertedImage();
}
return image;
}
case Mode::HighContrast:
case Mode::CustomColors:
{
for (int row = 0; row < image.height(); ++row)
{
for (int column = 0; column < image.width(); ++column)
{
QColor color = image.pixelColor(column, row);
QColor adjustedColor = convert(color, false, false);
image.setPixelColor(column, row, adjustedColor);
}
}
return image;
}
default:
Q_ASSERT(false);
break;
}
return image;
}
void PDFColorConvertor::setHighContrastBrightnessFactor(float factor)
{
m_sigmoidParamC = factor;
calculateSigmoidParams();
}
float PDFColorConvertor::correctLigthnessBySigmoidFunction(float lightness) const
{
const float adjustedLightness = sigmoidFunction(lightness);
const float normalizedLightness = (adjustedLightness - m_sigmoidParamC_Black) / m_sigmoidParamC_Range;
return qBound(0.0f, normalizedLightness, 1.0f);
}
float PDFColorConvertor::sigmoidFunction(float value) const
{
return 1.0f / (1.0f + std::expf(-m_sigmoidParamC * (value - 0.5f)));
}
float PDFColorConvertor::sigmoidParamC_Black() const
{
return sigmoidFunction(0.0);
}
float PDFColorConvertor::sigmoidParamC_White() const
{
return sigmoidFunction(1.0);
}
float PDFColorConvertor::sigmoidParamC_Range() const
{
return sigmoidParamC_White() - sigmoidParamC_Black();
}
void PDFColorConvertor::calculateSigmoidParams()
{
m_sigmoidParamC_Black = sigmoidParamC_Black();
m_sigmoidParamC_White = sigmoidParamC_White();
m_sigmoidParamC_Range = sigmoidParamC_Range();
}
int PDFColorConvertor::getBitonalThreshold() const
{
return m_bitonalThreshold;
}
void PDFColorConvertor::setBitonalThreshold(int newBitonalThreshold)
{
m_bitonalThreshold = newBitonalThreshold;
}
} // namespace pdf

View File

@ -0,0 +1,139 @@
// Copyright (C) 2023 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT 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
// with the written consent of the copyright owner, any later version.
//
// PDF4QT 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 PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#ifndef PDFCOLORCONVERTOR_H
#define PDFCOLORCONVERTOR_H
#include "pdfglobal.h"
#include <QColor>
#include <QImage>
namespace pdf
{
/// \class PDFColorConvertor
/// \brief Performs color conversions in the RGB color space.
/// This class supports multiple modes of operation, making it
/// useful for accessibility purposes, particularly for visually impaired users.
class PDF4QTLIBSHARED_EXPORT PDFColorConvertor
{
public:
PDFColorConvertor();
/// Enumeration for different modes of color conversion
enum class Mode
{
Normal, ///< No color conversion is performed.
InvertedColors, ///< Inverts colors in the RGB space.
Grayscale, ///< Converts colors to grayscale.
HighContrast, ///< Adjusts contrast using a sigmoid function.
Bitonal, ///< Creates a monochromatic (two-color) image.
CustomColors, ///< Applies custom foreground and background colors.
};
bool operator==(const PDFColorConvertor&) const = default;
bool operator!=(const PDFColorConvertor&) const = default;
/// Checks if color conversion is active.
/// \return `true` if color conversion is currently active; `false` otherwise.
/// When `false`, no color conversions are performed, and the `convert` function
/// operates as an identity function.
bool isActive() const;
/// Sets the mode for color conversion.
/// \param mode Specifies the new mode to be used for color conversion.
/// This mode determines how colors are transformed during the conversion process.
void setMode(Mode mode);
/// Converts the given color based on the current mode
/// \param color The QColor to be converted
/// \param background Use background color
/// \param foreground Use foreground color
/// \return The converted QColor
QColor convert(QColor color, bool background, bool foreground) const;
/// Converts the given image based on the current mode
/// \param image The image to be converted
/// \return The converted image
QImage convert(QImage image) const;
/// Sets the correction factor for enhancing contrast in high contrast mode.
/// This factor determines the level of contrast enhancement:
/// - For subtle enhancement, set the factor between 5 and 10.
/// - For moderate enhancement, use values between 10 and 20.
/// - For strong enhancement, choose values of 20 or higher.
/// \param factor The correction factor used for adjusting contrast.
void setHighContrastBrightnessFactor(float factor);
/// Retrieves the current bitonal threshold value used for determining lightness levels.
/// This threshold helps in differentiating between light and dark areas in an image processing context.
/// \returns The current bitonal threshold value as an integer. This value is used to classify pixels
/// as either light or dark based on their lightness.
int getBitonalThreshold() const;
/// Sets a new bitonal threshold value to be used in image processing.
/// This threshold determines how lightness levels are classified into binary categories (light or dark).
/// \param newBitonalThreshold The new threshold value as an integer. It should be chosen carefully
/// to ensure accurate differentiation between light and dark areas in images.
void setBitonalThreshold(int newBitonalThreshold);
private:
/// Correct lightness using sigmoid function
/// \return Adjusted lightness normalized in range [0.0, 1.0]
/// \param lightness Lightness in range [0.0, 1.0]
float correctLigthnessBySigmoidFunction(float lightness) const;
/// Calculates the value of the sigmoid function based on the given input.
/// The returned value is unscaled and should be scaled to the range [0.0, 1.0]
/// using other functions.
/// \param value The input value for the sigmoid function.
/// \return The unscaled result of the sigmoid function.
float sigmoidFunction(float value) const;
/// Determines the sigmoid function value for a completely black color
/// (i.e., when lightness is set to zero).
/// \return The sigmoid function value corresponding to black color.
float sigmoidParamC_Black() const;
/// Determines the sigmoid function value for a completely white color
/// (i.e., when lightness is set to one).
/// \return The sigmoid function value corresponding to white color.
float sigmoidParamC_White() const;
/// Determines the range of the sigmoid function's output,
/// useful for scaling the range to the normalized interval [0.0, 1.0].
/// \return The range of the sigmoid function's output.
float sigmoidParamC_Range() const;
/// Calculates parameters for the sigmoid function,
/// which are used to scale color values to the interval [0.0, 1.0].
void calculateSigmoidParams();
Mode m_mode = Mode::Normal;
float m_sigmoidParamC = 10.0f;
float m_sigmoidParamC_Black = 0.0f;
float m_sigmoidParamC_White = 1.0f;
float m_sigmoidParamC_Range = 1.0f;
int m_bitonalThreshold = 128;
QColor m_backgroundColor = Qt::black;
QColor m_foregroundColor = Qt::green;
};
} // namespace pdf
#endif // PDFCOLORCONVERTOR_H

View File

@ -767,11 +767,10 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
QColor PDFDrawWidgetProxy::getPaperColor()
{
QColor paperColor = getCMSManager()->getCurrentCMS()->getPaperColor();
if (m_features.testFlag(PDFRenderer::InvertColors))
{
paperColor = invertColor(paperColor);
}
PDFColorConvertor colorConvertor = getCMSManager()->getColorConvertor();
PDFRenderer::applyFeaturesToColorConvertor(getFeatures(), colorConvertor);
paperColor = colorConvertor.convert(paperColor, true, false);
return paperColor;
}

View File

@ -2241,7 +2241,7 @@ void PDFFormFieldComboBoxEditor::draw(AnnotationDrawParameters& parameters, bool
AnnotationDrawParameters listBoxParameters = parameters;
listBoxParameters.boundingRectangle = m_listBoxPopupRectangle;
QColor color = parameters.invertColors ? Qt::black : Qt::white;
QColor color = parameters.colorConvertor.convert(Qt::white, true, false);
listBoxParameters.painter->fillRect(listBoxParameters.boundingRectangle, color);
m_listBox.draw(listBoxParameters, true);
@ -2690,37 +2690,25 @@ void PDFListBoxPseudowidget::draw(AnnotationDrawParameters& parameters, bool edi
}
QPalette palette = QApplication::palette();
auto getAdjustedColor = [&parameters](QColor color)
{
if (parameters.invertColors)
{
return invertColor(color);
}
return color;
};
QTransform matrix = createListBoxTransformMatrix();
QPainter* painter = parameters.painter;
if (edit)
{
pdf::PDFPainterStateGuard guard2(painter);
painter->setPen(getAdjustedColor(Qt::black));
painter->setPen(parameters.colorConvertor.convert(Qt::black, false, true));
painter->setBrush(Qt::NoBrush);
painter->drawRect(parameters.boundingRectangle);
}
painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip);
painter->setWorldTransform(QTransform(matrix), true);
painter->setPen(getAdjustedColor(m_textColor));
painter->setPen(parameters.colorConvertor.convert(m_textColor, false, true));
painter->setFont(m_font);
QColor textColor = getAdjustedColor(m_textColor);
QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText));
QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight));
QColor textColor = parameters.colorConvertor.convert(m_textColor, false, true);
QColor highlightTextColor = parameters.colorConvertor.convert(palette.color(QPalette::HighlightedText), false, true);
QColor highlightColor = parameters.colorConvertor.convert(palette.color(QPalette::Highlight), false, false);
QRectF rect(0, 0, m_widgetRect.width(), m_lineSpacing);
for (int i = m_topIndex; i < int(m_options.size()); ++i)

View File

@ -706,7 +706,8 @@ void PDFCreatePCElementTextTool::drawPage(QPainter* painter,
parameters.painter = painter;
parameters.boundingRectangle = m_element->getRectangle();
parameters.key.first = PDFAppeareanceStreams::Appearance::Normal;
parameters.invertColors = getProxy()->getFeatures().testFlag(PDFRenderer::InvertColors);
parameters.colorConvertor = getProxy()->getCMSManager()->getColorConvertor();
PDFRenderer::applyFeaturesToColorConvertor(getProxy()->getFeatures(), parameters.colorConvertor);
painter->setWorldTransform(QTransform(pagePointToDevicePointMatrix), true);
m_textEditWidget->draw(parameters, true);

View File

@ -764,36 +764,41 @@ void PDFPrecompiledPage::optimize()
m_compositionModes.shrink_to_fit();
}
void PDFPrecompiledPage::invertColors()
void PDFPrecompiledPage::convertColors(const PDFColorConvertor& colorConvertor)
{
// Jakub Melka: we must invert colors in following areas:
// Jakub Melka: we must apply color convertor in following areas:
// - painter paths
// - images
// - meshes
if (!colorConvertor.isActive())
{
return;
}
for (PathPaintData& pathData : m_paths)
{
if (pathData.pen.style() != Qt::NoPen)
{
pathData.pen.setColor(invertColor(pathData.pen.color()));
pathData.pen.setColor(colorConvertor.convert(pathData.pen.color(), false, pathData.isText));
}
if (pathData.brush.style() == Qt::SolidPattern)
{
pathData.brush.setColor(invertColor(pathData.brush.color()));
pathData.brush.setColor(colorConvertor.convert(pathData.brush.color(), false, pathData.isText));
}
}
for (ImageData& imageData : m_images)
{
imageData.image.invertPixels(QImage::InvertRgb);
imageData.image = colorConvertor.convert(imageData.image);
}
for (MeshPaintData& meshPaintData : m_meshes)
{
meshPaintData.mesh.invertColors();
meshPaintData.mesh.convertColors(colorConvertor);
}
m_paperColor = invertColor(m_paperColor);
m_paperColor = colorConvertor.convert(m_paperColor, true, false);
}
void PDFPrecompiledPage::finalize(qint64 compilingTimeNS, QList<PDFRenderError> errors)

View File

@ -24,6 +24,7 @@
#include "pdfpagecontentprocessor.h"
#include "pdftextlayout.h"
#include "pdfsnapper.h"
#include "pdfcolorconvertor.h"
#include <QPen>
#include <QBrush>
@ -213,8 +214,8 @@ public:
/// Optimizes page memory allocation to contain less space
void optimize();
/// Inverts all colors
void invertColors();
/// Converts all colors
void convertColors(const PDFColorConvertor& colorConvertor);
/// Finalizes precompiled page
/// \param compilingTimeNS Compiling time in nanoseconds

View File

@ -1406,14 +1406,16 @@ qint64 PDFMesh::getMemoryConsumptionEstimate() const
return memoryConsumption;
}
void PDFMesh::invertColors()
void PDFMesh::convertColors(const PDFColorConvertor& colorConvertor)
{
for (Triangle& triangle : m_triangles)
{
triangle.color = 0x00FFFFFF - triangle.color;
QColor color = QColor::fromRgb(triangle.color);
QColor adjustedColor = colorConvertor.convert(color, false, false);
triangle.color = adjustedColor.rgb();
}
m_backgroundColor = invertColor(m_backgroundColor);
m_backgroundColor = colorConvertor.convert(m_backgroundColor, true, false);
}
void PDFMeshQualitySettings::initResolution()

View File

@ -22,6 +22,7 @@
#include "pdffunction.h"
#include "pdfcolorspaces.h"
#include "pdfmeshqualitysettings.h"
#include "pdfcolorconvertor.h"
#include <QTransform>
#include <QPainterPath>
@ -144,8 +145,8 @@ public:
/// Returns estimate of number of bytes, which this mesh occupies in memory
qint64 getMemoryConsumptionEstimate() const;
/// Invert colors
void invertColors();
/// Apply color conversion
void convertColors(const PDFColorConvertor& colorConvertor);
private:
std::vector<QPointF> m_vertices;

View File

@ -115,6 +115,36 @@ QTransform PDFRenderer::createMediaBoxToDevicePointMatrix(const QRectF& mediaBox
return matrix;
}
void PDFRenderer::applyFeaturesToColorConvertor(const Features& features, PDFColorConvertor& convertor)
{
convertor.setMode(PDFColorConvertor::Mode::Normal);
if (features.testFlag(ColorAdjust_Invert))
{
convertor.setMode(PDFColorConvertor::Mode::InvertedColors);
}
if (features.testFlag(ColorAdjust_Grayscale))
{
convertor.setMode(PDFColorConvertor::Mode::Grayscale);
}
if (features.testFlag(ColorAdjust_HighContrast))
{
convertor.setMode(PDFColorConvertor::Mode::HighContrast);
}
if (features.testFlag(ColorAdjust_Bitonal))
{
convertor.setMode(PDFColorConvertor::Mode::Bitonal);
}
if (features.testFlag(ColorAdjust_CustomColors))
{
convertor.setMode(PDFColorConvertor::Mode::CustomColors);
}
}
const PDFOperationControl* PDFRenderer::getOperationControl() const
{
return m_operationControl;
@ -181,10 +211,9 @@ void PDFRenderer::compile(PDFPrecompiledPage* precompiledPage, size_t pageIndex)
generator.setOperationControl(m_operationControl);
QList<PDFRenderError> errors = generator.processContents();
if (m_features.testFlag(InvertColors))
{
precompiledPage->invertColors();
}
PDFColorConvertor colorConvertor = m_cms->getColorConvertor();
PDFRenderer::applyFeaturesToColorConvertor(m_features, colorConvertor);
precompiledPage->convertColors(colorConvertor);
precompiledPage->optimize();
precompiledPage->finalize(timer.nsecsElapsed(), qMove(errors));

View File

@ -23,6 +23,7 @@
#include "pdfoperationcontrol.h"
#include "pdfmeshqualitysettings.h"
#include "pdfutils.h"
#include "pdfcolorconvertor.h"
#include <QMutex>
#include <QSemaphore>
@ -85,10 +86,15 @@ public:
DisplayTimes = 0x0020, ///< Display page compile/draw time
DebugTextBlocks = 0x0040, ///< Debug text block layout algorithm
DebugTextLines = 0x0080, ///< Debug text line layout algorithm
InvertColors = 0x0100, ///< Invert colors
DenyExtraGraphics = 0x0200, ///< Do not display additional graphics, for example from tools
DisplayAnnotations = 0x0400, ///< Display annotations
LogicalSizeZooming = 0x0800, ///< Use logical pixel resolution instead of physical one when zooming
DenyExtraGraphics = 0x0100, ///< Do not display additional graphics, for example from tools
DisplayAnnotations = 0x0200, ///< Display annotations
LogicalSizeZooming = 0x0400, ///< Use logical pixel resolution instead of physical one when zooming
ColorAdjust_Invert = 0x0800, ///< Invert colors
ColorAdjust_Grayscale = 0x1000, ///< Convert colors to grayscale
ColorAdjust_HighContrast = 0x2000, ///< Convert colors to high constrast colors
ColorAdjust_Bitonal = 0x4000, ///< Convert colors to bitonal (monochromatic)
ColorAdjust_CustomColors = 0x8000, ///< Convert colors to custom color settings
};
Q_DECLARE_FLAGS(Features, Feature)
@ -137,9 +143,15 @@ public:
const QRectF& rectangle,
PageRotation rotation);
/// Applies rendering flags to the color convertor
static void applyFeaturesToColorConvertor(const Features& features, PDFColorConvertor& convertor);
/// Returns default renderer features
static constexpr Features getDefaultFeatures() { return Features(Antialiasing | TextAntialiasing | ClipToCropBox | DisplayAnnotations); }
/// Returns color transformation features
static constexpr Features getColorFeatures() { return Features(ColorAdjust_Invert | ColorAdjust_Grayscale | ColorAdjust_HighContrast | ColorAdjust_Bitonal | ColorAdjust_CustomColors); }
const PDFOperationControl* getOperationControl() const;
void setOperationControl(const PDFOperationControl* newOperationControl);

View File

@ -640,30 +640,19 @@ void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool ed
}
QPalette palette = QApplication::palette();
auto getAdjustedColor = [&parameters](QColor color)
{
if (parameters.invertColors)
{
return invertColor(color);
}
return color;
};
QPainter* painter = parameters.painter;
if (edit)
{
pdf::PDFPainterStateGuard guard2(painter);
painter->setPen(getAdjustedColor(Qt::black));
painter->setPen(parameters.colorConvertor.convert(Qt::black, false, true));
painter->setBrush(Qt::NoBrush);
painter->drawRect(parameters.boundingRectangle);
}
painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip);
painter->setWorldTransform(QTransform(createTextBoxTransformMatrix(edit)), true);
painter->setPen(getAdjustedColor(Qt::black));
painter->setPen(parameters.colorConvertor.convert(Qt::black, false, true));
if (isComb())
{
@ -672,9 +661,9 @@ void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool ed
QRectF combRect(0.0, 0.0, combWidth, m_widgetRect.height());
painter->setFont(m_textLayout.font());
QColor textColor = getAdjustedColor(m_textColor);
QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText));
QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight));
QColor textColor = parameters.colorConvertor.convert(m_textColor, false, true);
QColor highlightTextColor = parameters.colorConvertor.convert(palette.color(QPalette::HighlightedText), false, true);
QColor highlightColor = parameters.colorConvertor.convert(palette.color(QPalette::Highlight), false, false);
std::vector<int> positions = getCursorPositions();
for (size_t i = 1; i < positions.size(); ++i)
@ -720,7 +709,7 @@ void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool ed
defaultFormat.start = getPositionStart();
defaultFormat.length = getTextLength();
defaultFormat.format.clearBackground();
defaultFormat.format.setForeground(QBrush(getAdjustedColor(m_textColor), Qt::SolidPattern));
defaultFormat.format.setForeground(QBrush(parameters.colorConvertor.convert(m_textColor, false, true), Qt::SolidPattern));
// If we are editing, draw selections
if (edit && isTextSelected())
@ -736,8 +725,8 @@ void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool ed
QTextLayout::FormatRange selectedFormat = defaultFormat;
selectedFormat.start = m_selectionStart;
selectedFormat.length = getSelectionLength();
selectedFormat.format.setForeground(QBrush(getAdjustedColor(palette.color(QPalette::HighlightedText)), Qt::SolidPattern));
selectedFormat.format.setBackground(QBrush(getAdjustedColor(palette.color(QPalette::Highlight)), Qt::SolidPattern));
selectedFormat.format.setForeground(QBrush(parameters.colorConvertor.convert(palette.color(QPalette::HighlightedText), false, true), Qt::SolidPattern));
selectedFormat.format.setBackground(QBrush(parameters.colorConvertor.convert(palette.color(QPalette::Highlight), false, false), Qt::SolidPattern));
selections = { before, selectedFormat, after};
}

View File

@ -141,6 +141,10 @@ std::vector<QAction*> PDFActionManager::getRenderingOptionActions() const
RenderOptionIgnoreOptionalContentSettings,
RenderOptionDisplayAnnotations,
RenderOptionInvertColors,
RenderOptionGrayscale,
RenderOptionBitonal,
RenderOptionHighContrast,
RenderOptionCustomColors,
RenderOptionShowTextBlocks,
RenderOptionShowTextLines
});
@ -249,7 +253,11 @@ void PDFActionManager::initActions(QSize iconSize, bool initializeStampActions)
setUserData(RenderOptionSmoothPictures, pdf::PDFRenderer::SmoothImages);
setUserData(RenderOptionIgnoreOptionalContentSettings, pdf::PDFRenderer::IgnoreOptionalContent);
setUserData(RenderOptionDisplayAnnotations, pdf::PDFRenderer::DisplayAnnotations);
setUserData(RenderOptionInvertColors, pdf::PDFRenderer::InvertColors);
setUserData(RenderOptionInvertColors, pdf::PDFRenderer::ColorAdjust_Invert);
setUserData(RenderOptionGrayscale, pdf::PDFRenderer::ColorAdjust_Grayscale);
setUserData(RenderOptionBitonal, pdf::PDFRenderer::ColorAdjust_Bitonal);
setUserData(RenderOptionHighContrast, pdf::PDFRenderer::ColorAdjust_HighContrast);
setUserData(RenderOptionCustomColors, pdf::PDFRenderer::ColorAdjust_CustomColors);
setUserData(RenderOptionShowTextBlocks, pdf::PDFRenderer::DebugTextBlocks);
setUserData(RenderOptionShowTextLines, pdf::PDFRenderer::DebugTextLines);
@ -1051,8 +1059,17 @@ void PDFProgramController::onActionRenderingOptionTriggered(bool checked)
Q_ASSERT(action);
pdf::PDFRenderer::Features features = m_settings->getFeatures();
features.setFlag(static_cast<pdf::PDFRenderer::Feature>(action->data().toInt()), checked);
pdf::PDFRenderer::Feature affectedFeature = static_cast<pdf::PDFRenderer::Feature>(action->data().toInt());
pdf::PDFRenderer::Features colorFeatures = pdf::PDFRenderer::getColorFeatures();
if (colorFeatures.testFlag(affectedFeature) && checked)
{
features = features & ~colorFeatures;
}
features.setFlag(affectedFeature, checked);
m_settings->setFeatures(features);
updateRenderingOptionActions();
}
void PDFProgramController::performSaveAs()

View File

@ -160,6 +160,10 @@ public:
RenderOptionIgnoreOptionalContentSettings,
RenderOptionDisplayAnnotations,
RenderOptionInvertColors,
RenderOptionGrayscale,
RenderOptionBitonal,
RenderOptionHighContrast,
RenderOptionCustomColors,
RenderOptionShowTextBlocks,
RenderOptionShowTextLines,
PageLayoutSinglePage,

View File

@ -150,7 +150,11 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
m_actionManager->setAction(PDFActionManager::RenderOptionSmoothPictures, ui->actionRenderOptionSmoothPictures);
m_actionManager->setAction(PDFActionManager::RenderOptionIgnoreOptionalContentSettings, ui->actionRenderOptionIgnoreOptionalContentSettings);
m_actionManager->setAction(PDFActionManager::RenderOptionDisplayAnnotations, ui->actionRenderOptionDisplayAnnotations);
m_actionManager->setAction(PDFActionManager::RenderOptionInvertColors, ui->actionInvertColors);
m_actionManager->setAction(PDFActionManager::RenderOptionInvertColors, ui->actionColorInvert);
m_actionManager->setAction(PDFActionManager::RenderOptionGrayscale, ui->actionColorGrayscale);
m_actionManager->setAction(PDFActionManager::RenderOptionHighContrast, ui->actionColorHighContrast);
m_actionManager->setAction(PDFActionManager::RenderOptionBitonal, ui->actionColorBitonal);
m_actionManager->setAction(PDFActionManager::RenderOptionCustomColors, ui->actionColorCustom);
m_actionManager->setAction(PDFActionManager::RenderOptionShowTextBlocks, ui->actionShow_Text_Blocks);
m_actionManager->setAction(PDFActionManager::RenderOptionShowTextLines, ui->actionShow_Text_Lines);
m_actionManager->setAction(PDFActionManager::Properties, ui->actionProperties);

View File

@ -91,7 +91,11 @@
<addaction name="actionFitWidth"/>
<addaction name="actionFitHeight"/>
<addaction name="separator"/>
<addaction name="actionInvertColors"/>
<addaction name="actionColorInvert"/>
<addaction name="actionColorGrayscale"/>
<addaction name="actionColorHighContrast"/>
<addaction name="actionColorBitonal"/>
<addaction name="actionColorCustom"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
@ -527,7 +531,7 @@
<string>Copy text</string>
</property>
</action>
<action name="actionInvertColors">
<action name="actionColorInvert">
<property name="checkable">
<bool>true</bool>
</property>
@ -536,7 +540,55 @@
<normaloff>:/resources/invert-colors.svg</normaloff>:/resources/invert-colors.svg</iconset>
</property>
<property name="text">
<string>Invert Colors</string>
<string>Color | Inverted</string>
</property>
</action>
<action name="actionColorGrayscale">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="pdf4qtviewer.qrc">
<normaloff>:/resources/invert-colors.svg</normaloff>:/resources/invert-colors.svg</iconset>
</property>
<property name="text">
<string>Color | Grayscale</string>
</property>
</action>
<action name="actionColorHighContrast">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="pdf4qtviewer.qrc">
<normaloff>:/resources/invert-colors.svg</normaloff>:/resources/invert-colors.svg</iconset>
</property>
<property name="text">
<string>Color | High Contrast</string>
</property>
</action>
<action name="actionColorBitonal">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="pdf4qtviewer.qrc">
<normaloff>:/resources/invert-colors.svg</normaloff>:/resources/invert-colors.svg</iconset>
</property>
<property name="text">
<string>Color | Monochromatic</string>
</property>
</action>
<action name="actionColorCustom">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="pdf4qtviewer.qrc">
<normaloff>:/resources/invert-colors.svg</normaloff>:/resources/invert-colors.svg</iconset>
</property>
<property name="text">
<string>Color | Custom</string>
</property>
</action>
<action name="actionRotateRight">

View File

@ -1167,7 +1167,11 @@ std::vector<PDFToolOptions::RenderFeatureInfo> PDFToolOptions::getRenderFeatures
RenderFeatureInfo{ "render-smooth-img", "Smooth image transformation (slower, but better quality images).", pdf::PDFRenderer::SmoothImages },
RenderFeatureInfo{ "render-ignore-opt-content", "Ignore optional content settings (draw everything).", pdf::PDFRenderer::IgnoreOptionalContent },
RenderFeatureInfo{ "render-clip-to-crop-box", "Clip page graphics to crop box.", pdf::PDFRenderer::ClipToCropBox },
RenderFeatureInfo{ "render-invert-colors", "Invert all colors.", pdf::PDFRenderer::InvertColors },
RenderFeatureInfo{ "render-invert-colors", "Color conversion: invert all colors.", pdf::PDFRenderer::ColorAdjust_Invert },
RenderFeatureInfo{ "render-grayscale", "Color conversion: convert to grayscale", pdf::PDFRenderer::ColorAdjust_Grayscale },
RenderFeatureInfo{ "render-high-contrast", "Color conversion: high contrast colors", pdf::PDFRenderer::ColorAdjust_HighContrast },
RenderFeatureInfo{ "render-bitonal", "Color conversion: bitonal page image", pdf::PDFRenderer::ColorAdjust_Bitonal },
RenderFeatureInfo{ "render-custom-colors", "Color conversion: custom colors", pdf::PDFRenderer::ColorAdjust_CustomColors },
RenderFeatureInfo{ "render-display-annot", "Display annotations.", pdf::PDFRenderer::DisplayAnnotations }
};
}