Blend functions

This commit is contained in:
Jakub Melka 2021-01-14 19:33:23 +01:00
parent ccb84401db
commit d3827cfcc4
8 changed files with 452 additions and 20 deletions

View File

@ -17,6 +17,8 @@
#include "pdfblendfunction.h"
#include <algorithm>
namespace pdf
{
@ -197,4 +199,227 @@ std::vector<BlendMode> PDFBlendModeInfo::getBlendModes()
};
}
PDFColorComponent PDFBlendFunction::blend(BlendMode mode, PDFColorComponent Cb, PDFColorComponent Cs)
{
switch (mode)
{
case BlendMode::Normal:
case BlendMode::Compatible:
return Cs;
case BlendMode::Multiply:
return Cb * Cs;
case BlendMode::Screen:
return Cb + Cs - Cb * Cs;
case BlendMode::Overlay:
return blend(BlendMode::HardLight, Cs, Cb);
case BlendMode::Darken:
return qMin(Cb, Cs);
case BlendMode::Lighten:
return qMax(Cb, Cs);
case BlendMode::ColorDodge:
{
if (qFuzzyIsNull(Cb))
{
return 0.0f;
}
const PDFColorComponent CsInverted = 1.0f - Cs;
if (Cb >= CsInverted)
{
return 1.0f;
}
return Cb / CsInverted;
}
case BlendMode::ColorBurn:
{
const PDFColorComponent CbInverted = 1.0f - Cb;
if (qFuzzyIsNull(CbInverted))
{
return 1.0f;
}
if (CbInverted >= Cs)
{
return 0.0f;
}
return 1.0f - CbInverted / Cs;
}
case BlendMode::HardLight:
{
if (Cs <= 0.5f)
{
return blend(BlendMode::Multiply, Cb, 2.0f * Cs);
}
else
{
return blend(BlendMode::Screen, Cb, 2.0f * Cs - 1.0f);
}
}
case BlendMode::SoftLight:
{
if (Cs <= 0.5f)
{
return Cb - (1.0f - 2.0f * Cs) * Cb * (1.0f - Cb);
}
else
{
PDFColorComponent D = 0.0f;
if (Cb <= 0.25)
{
D = ((16.0f * Cb - 12.0f) * Cb + 4.0f) * Cb;
}
else
{
D = std::sqrt(Cb);
}
return Cb + (2.0f * Cs - 1.0f) * (D - Cb);
}
}
case BlendMode::Difference:
return qAbs(Cb - Cs);
case BlendMode::Exclusion:
return Cb + Cs - 2.0f * Cb * Cs;
default:
{
Q_ASSERT(false);
break;
}
}
return Cs;
}
PDFRGB PDFBlendFunction::blend_Hue(PDFRGB Cb, PDFRGB Cs)
{
return nonseparable_SetLum(nonseparable_SetSat(Cs, nonseparable_Sat(Cb)), nonseparable_Lum(Cb));
}
PDFRGB PDFBlendFunction::blend_Saturation(PDFRGB Cb, PDFRGB Cs)
{
return nonseparable_SetLum(nonseparable_SetSat(Cb, nonseparable_Sat(Cs)), nonseparable_Lum(Cb));
}
PDFRGB PDFBlendFunction::blend_Color(PDFRGB Cb, PDFRGB Cs)
{
return nonseparable_SetLum(Cs, nonseparable_Lum(Cb));
}
PDFRGB PDFBlendFunction::blend_Luminosity(PDFRGB Cb, PDFRGB Cs)
{
return nonseparable_SetLum(Cb, nonseparable_Lum(Cs));
}
PDFRGB PDFBlendFunction::nonseparable_gray2rgb(PDFGray gray)
{
return PDFRGB{ gray, gray, gray };
}
PDFGray PDFBlendFunction::nonseparable_rgb2gray(PDFRGB rgb)
{
// Just convert to luminosity
return nonseparable_Lum(rgb);
}
PDFRGB PDFBlendFunction::nonseparable_cmyk2rgb(PDFCMYK cmyk)
{
return PDFRGB{ 1.0f - cmyk[0], 1.0f - cmyk[1], 1.0f - cmyk[2] };
}
PDFCMYK PDFBlendFunction::nonseparable_rgb2cmyk(PDFRGB rgb, PDFColorComponent K)
{
return PDFCMYK{ 1.0f - rgb[0], 1.0f - rgb[1], 1.0f - rgb[2], K };
}
PDFColorComponent PDFBlendFunction::nonseparable_Lum(PDFRGB rgb)
{
return 0.30 * rgb[0] + 0.59 * rgb[1] + 0.11 * rgb[2];
}
PDFColorComponent PDFBlendFunction::nonseparable_Sat(PDFRGB rgb)
{
const PDFColorComponent min = *std::min_element(rgb.cbegin(), rgb.cend());
const PDFColorComponent max = *std::max_element(rgb.cbegin(), rgb.cend());
return max - min;
}
PDFRGB PDFBlendFunction::nonseparable_SetLum(PDFRGB C, PDFColorComponent l)
{
const PDFColorComponent d = l - nonseparable_Lum(C);
PDFRGB result = C;
result[0] += d;
result[1] += d;
result[2] += d;
return nonseparable_ClipColor(result);
}
PDFRGB PDFBlendFunction::nonseparable_SetSat(PDFRGB C, PDFColorComponent s)
{
auto it_min = std::min_element(C.begin(), C.end());
auto it_max = std::max_element(C.begin(), C.end());
auto it_mid = C.end();
for (auto it = C.begin(); it != C.end(); ++it)
{
if (it != it_min && it != it_max)
{
it_mid = it;
break;
}
}
Q_ASSERT(it_mid != C.end());
PDFRGB result = C;
if (*it_max > *it_min)
{
*it_mid = (*it_mid - *it_min) * s / (*it_max - *it_min);
*it_max = s;
result = C;
}
else
{
std::fill(result.begin(), result.end(), 0.0f);
}
return result;
}
PDFRGB PDFBlendFunction::nonseparable_ClipColor(PDFRGB C)
{
PDFRGB result = C;
const PDFColorComponent l = nonseparable_Lum(C);
const PDFColorComponent n = *std::min_element(C.cbegin(), C.cend());
const PDFColorComponent x = *std::max_element(C.cbegin(), C.cend());
if (n < 0.0f)
{
const PDFColorComponent factor = 1.0f / (l - n);
result[0] = l + (result[0] - l) * l * factor;
result[1] = l + (result[1] - l) * l * factor;
result[2] = l + (result[2] - l) * l * factor;
}
if (x > 1.0f)
{
const PDFColorComponent factor = 1.0f / (x - l);
result[0] = l + (result[0] - l) * (1.0f - l) * factor;
result[1] = l + (result[1] - l) * (1.0f - l) * factor;
result[2] = l + (result[2] - l) * (1.0f - l) * factor;
}
return result;
}
} // namespace pdf

View File

@ -88,6 +88,50 @@ public:
static std::vector<BlendMode> getBlendModes();
};
/// Class grouping together blend functions. Can also blend non-separable blend modes,
/// such as Color, Hue, Saturation and Luminosity, according 11.3.5.3 of PDF 2.0 specification.
class PDFBlendFunction
{
public:
PDFBlendFunction() = delete;
/// Blend function used to blend separable blend modes
/// \param Cb Backdrop color
/// \param Cs Source color
static PDFColorComponent blend(BlendMode mode, PDFColorComponent Cb, PDFColorComponent Cs);
/// Blend non-separable hue function
/// \param Cb Backdrop color
/// \param Cs Source color
static PDFRGB blend_Hue(PDFRGB Cb, PDFRGB Cs);
/// Blend non-separable saturation function
/// \param Cb Backdrop color
/// \param Cs Source color
static PDFRGB blend_Saturation(PDFRGB Cb, PDFRGB Cs);
/// Blend non-separable color function
/// \param Cb Backdrop color
/// \param Cs Source color
static PDFRGB blend_Color(PDFRGB Cb, PDFRGB Cs);
/// Blend non-separable luminosity function
/// \param Cb Backdrop color
/// \param Cs Source color
static PDFRGB blend_Luminosity(PDFRGB Cb, PDFRGB Cs);
private:
static PDFRGB nonseparable_gray2rgb(PDFGray gray);
static PDFGray nonseparable_rgb2gray(PDFRGB rgb);
static PDFRGB nonseparable_cmyk2rgb(PDFCMYK cmyk);
static PDFCMYK nonseparable_rgb2cmyk(PDFRGB rgb, PDFColorComponent K);
static PDFColorComponent nonseparable_Lum(PDFRGB rgb);
static PDFColorComponent nonseparable_Sat(PDFRGB rgb);
static PDFRGB nonseparable_SetLum(PDFRGB C, PDFColorComponent l);
static PDFRGB nonseparable_SetSat(PDFRGB C, PDFColorComponent s);
static PDFRGB nonseparable_ClipColor(PDFRGB C);
};
} // namespace pdf
#endif // PDFBLENDFUNCTION_H

View File

@ -41,7 +41,6 @@ class PDFAbstractColorSpace;
class PDFPatternColorSpace;
class PDFRenderErrorReporter;
using PDFColorComponent = float;
using PDFColor = PDFFlatArray<PDFColorComponent, 4>;
using PDFColorSpacePointer = QSharedPointer<PDFAbstractColorSpace>;
using PDFColorBuffer = PDFBuffer<PDFColorComponent>;

View File

@ -24,6 +24,7 @@
#include <limits>
#include <tuple>
#include <array>
#if defined(Pdf4QtLIB_LIBRARY)
# define Pdf4QtLIBSHARED_EXPORT Q_DECL_EXPORT
@ -36,6 +37,10 @@ namespace pdf
using PDFInteger = int64_t;
using PDFReal = double;
using PDFColorComponent = float;
using PDFGray = PDFColorComponent;
using PDFRGB = std::array<PDFColorComponent, 3>;
using PDFCMYK = std::array<PDFColorComponent, 4>;
// These constants define minimum/maximum integer and are defined in such a way,
// that even 100 times bigger integers are representable.

View File

@ -455,6 +455,32 @@ bool PDFPageContentProcessor::isContentSuppressed() const
return std::any_of(m_markedContentStack.cbegin(), m_markedContentStack.cend(), [](const MarkedContentState& state) { return state.contentSuppressed; });
}
PDFPageContentProcessor::PDFTransparencyGroup PDFPageContentProcessor::parseTransparencyGroup(const PDFObject& object)
{
PDFTransparencyGroup group;
if (const PDFDictionary* transparencyDictionary = m_document->getDictionaryFromObject(object))
{
const PDFObject& colorSpaceObject = m_document->getObject(transparencyDictionary->get("CS"));
if (!colorSpaceObject.isNull())
{
group.colorSpacePointer = PDFAbstractColorSpace::createColorSpace(m_colorSpaceDictionary, m_document, colorSpaceObject);
if (group.colorSpacePointer && !group.colorSpacePointer->isBlendColorSpace())
{
reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Transparency group blending color space is invalid."));
group.colorSpacePointer = nullptr;
}
}
PDFDocumentDataLoaderDecorator loader(m_document);
group.isolated = loader.readBooleanFromDictionary(transparencyDictionary, "I", false);
group.knockout = loader.readBooleanFromDictionary(transparencyDictionary, "K", false);
}
return group;
}
void PDFPageContentProcessor::processContent(const QByteArray& content)
{
PDFLexicalAnalyzer parser(content.constBegin(), content.constEnd());

View File

@ -607,6 +607,25 @@ protected:
/// only for compatibility purposes. See chapter 14.2 in PDF 2.0 specification.
ProcedureSets getProcedureSets() const { return m_procedureSets; }
/// Returns page
const PDFPage* getPage() const { return m_page; }
/// Returns document
const PDFDocument* getDocument() const { return m_document; }
/// Parses transparency group
PDFTransparencyGroup parseTransparencyGroup(const PDFObject& object);
class PDFTransparencyGroupGuard
{
public:
explicit PDFTransparencyGroupGuard(PDFPageContentProcessor* processor, PDFTransparencyGroup&& group);
~PDFTransparencyGroupGuard();
private:
PDFPageContentProcessor* m_processor;
};
private:
/// Initializes the resources dictionaries
void initDictionaries(const PDFObject& resourcesObject);
@ -701,16 +720,6 @@ private:
PDFPageContentProcessor* m_processor;
};
class PDFTransparencyGroupGuard
{
public:
explicit PDFTransparencyGroupGuard(PDFPageContentProcessor* processor, PDFTransparencyGroup&& group);
~PDFTransparencyGroupGuard();
private:
PDFPageContentProcessor* m_processor;
};
/// Wrapper for PDF Name
struct PDFOperandName
{

View File

@ -16,6 +16,7 @@
// along with Pdf4Qt. If not, see <https://www.gnu.org/licenses/>.
#include "pdftransparencyrenderer.h"
#include "pdfdocument.h"
namespace pdf
{
@ -41,7 +42,8 @@ PDFFloatBitmap::PDFFloatBitmap(size_t width, size_t height, PDFPixelFormat forma
PDFColorBuffer PDFFloatBitmap::getPixel(size_t x, size_t y)
{
s
const size_t index = getPixelIndex(x, y);
return PDFColorBuffer(m_data.data() + index, m_pixelSize);
}
const PDFColorComponent* PDFFloatBitmap::begin() const
@ -64,9 +66,78 @@ PDFColorComponent* PDFFloatBitmap::end()
return m_data.data() + m_data.size();
}
PDFTransparencyRenderer::PDFTransparencyRenderer()
size_t PDFFloatBitmap::getPixelIndex(size_t x, size_t y) const
{
return (y * m_width + x) * m_pixelSize;
}
PDFTransparencyRenderer::PDFTransparencyRenderer(const PDFPage* page,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFCMS* cms,
const PDFOptionalContentActivity* optionalContentActivity,
QMatrix pagePointToDevicePointMatrix) :
BaseClass(page, document, fontCache, cms, optionalContentActivity, pagePointToDevicePointMatrix, PDFMeshQualitySettings()),
m_active(false)
{
}
void PDFTransparencyRenderer::setDeviceColorSpace(PDFColorSpacePointer colorSpace)
{
if (!colorSpace || colorSpace->isBlendColorSpace())
{
// Set device color space only, when it is a blend color space
m_deviceColorSpace = colorSpace;
}
}
void PDFTransparencyRenderer::beginPaint()
{
Q_ASSERT(!m_active);
m_active = true;
// Create page transparency group
PDFObject pageTransparencyGroupObject = getPage()->getTransparencyGroup(&getDocument()->getStorage());
PDFTransparencyGroup transparencyGroup = parseTransparencyGroup(pageTransparencyGroupObject);
transparencyGroup.isolated = true;
m_pageTransparencyGroupGuard.reset(new PDFTransparencyGroupGuard(this, qMove(transparencyGroup)));
}
const PDFFloatBitmap& PDFTransparencyRenderer::endPaint()
{
Q_ASSERT(m_active);
m_pageTransparencyGroupGuard.reset();
m_active = false;
}
void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule)
{
}
void PDFTransparencyRenderer::performClipping(const QPainterPath& path, Qt::FillRule fillRule)
{
}
void PDFTransparencyRenderer::performUpdateGraphicsState(const PDFPageContentProcessorState& state)
{
}
void PDFTransparencyRenderer::performSaveGraphicState(ProcessOrder order)
{
}
void PDFTransparencyRenderer::performRestoreGraphicState(ProcessOrder order)
{
}
void PDFTransparencyRenderer::performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup)
{
}
void PDFTransparencyRenderer::performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup)
{
}
} // namespace pdf

View File

@ -20,6 +20,7 @@
#include "pdfglobal.h"
#include "pdfcolorspaces.h"
#include "pdfpagecontentprocessor.h"
namespace pdf
{
@ -36,7 +37,7 @@ class PDFPixelFormat
public:
inline explicit constexpr PDFPixelFormat() = default;
constexpr uint8_t INVALID_CHANNEL_INDEX = 0xFF;
constexpr static uint8_t INVALID_CHANNEL_INDEX = 0xFF;
constexpr bool hasProcessColors() const { return m_processColors > 0; }
constexpr bool hasSpotColors() const { return m_spotColors > 0; }
@ -96,9 +97,9 @@ private:
}
constexpr uint8_t FLAG_HAS_SHAPE_CHANNEL = 0x01;
constexpr uint8_t FLAG_HAS_OPACITY_CHANNEL = 0x02;
constexpr uint8_t FLAG_PROCESS_COLORS_SUBTRACTIVE = 0x04;
constexpr static uint8_t FLAG_HAS_SHAPE_CHANNEL = 0x01;
constexpr static uint8_t FLAG_HAS_OPACITY_CHANNEL = 0x02;
constexpr static uint8_t FLAG_PROCESS_COLORS_SUBTRACTIVE = 0x04;
uint8_t m_processColors = 0;
uint8_t m_spotColors = 0;
@ -113,7 +114,7 @@ public:
explicit PDFFloatBitmap();
explicit PDFFloatBitmap(size_t width, size_t height, PDFPixelFormat format);
/// Returns buffer with pixel
/// Returns buffer with pixel channels
PDFColorBuffer getPixel(size_t x, size_t y);
const PDFColorComponent* begin() const;
@ -135,10 +136,62 @@ private:
std::vector<PDFColorComponent> m_data;
};
class PDFTransparencyRenderer
/// Renders PDF pages with transparency, using 32-bit floating point precision.
/// Both device color space and blending color space can be defined. It implements
/// page blending space and device blending space. So, painted graphics is being
/// blended to the page blending space, and then converted to the device blending
/// space.
class PDFTransparencyRenderer : public PDFPageContentProcessor
{
private:
using BaseClass = PDFPageContentProcessor;
public:
PDFTransparencyRenderer();
PDFTransparencyRenderer(const PDFPage* page,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFCMS* cms,
const PDFOptionalContentActivity* optionalContentActivity,
QMatrix pagePointToDevicePointMatrix);
void setDeviceColorSpace(PDFColorSpacePointer colorSpace);
/// Starts painting on the device. This function must be called before page
/// content stream is being processed (and must be called exactly once).
void beginPaint();
/// Finishes painting on the device. This function must be called after page
/// content stream is processed and all result graphics is being drawn. Page
/// transparency group collapses nad contents are draw onto device transparency
/// group.
const PDFFloatBitmap& endPaint();
virtual void performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) override;
virtual void performClipping(const QPainterPath& path, Qt::FillRule fillRule) override;
virtual void performUpdateGraphicsState(const PDFPageContentProcessorState& state) override;
virtual void performSaveGraphicState(ProcessOrder order) override;
virtual void performRestoreGraphicState(ProcessOrder order) override;
virtual void performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) override;
virtual void performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup) override;
private:
struct PDFTransparencyGroupPainterData
{
PDFTransparencyGroup group;
bool alphaIsShape = false;
PDFReal alphaStroke = 1.0;
PDFReal alphaFill = 1.0;
BlendMode blendMode = BlendMode::Normal;
BlackPointCompensationMode blackPointCompensationMode = BlackPointCompensationMode::Default;
RenderingIntent RenderingIntent = RenderingIntent::RelativeColorimetric;
};
PDFColorSpacePointer m_deviceColorSpace; ///< Device color space (color space for final result)
PDFColorSpacePointer m_processColorSpace; ///< Process color space (color space, in which is page graphic's blended)
std::unique_ptr<PDFTransparencyGroupGuard> m_pageTransparencyGroupGuard;
std::vector<PDFTransparencyGroupPainterData> m_transparencyGroupDataStack;
bool m_active;
};
} // namespace pdf