mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-02-28 17:37:46 +01:00
ImageMask 1-bit images
This commit is contained in:
parent
630afbba61
commit
84f26180c5
@ -103,13 +103,20 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const
|
||||
throw PDFParserException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount));
|
||||
}
|
||||
|
||||
const std::vector<PDFReal>& decode = imageData.getDecode();
|
||||
if (!decode.empty() && decode.size() != componentCount * 2)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Invalid size of the decoded array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size()));
|
||||
}
|
||||
|
||||
QDataStream stream(const_cast<QByteArray*>(&imageData.getData()), QIODevice::ReadOnly);
|
||||
PDFBitReader reader(&stream, imageData.getBitsPerComponent());
|
||||
|
||||
PDFColor color;
|
||||
color.resize(componentCount);
|
||||
|
||||
const double coefficient = 1.0 / reader.max();
|
||||
const double max = reader.max();
|
||||
const double coefficient = 1.0 / max;
|
||||
for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i)
|
||||
{
|
||||
reader.seek(i * imageData.getStride());
|
||||
@ -119,8 +126,17 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const
|
||||
{
|
||||
for (unsigned int k = 0; k < componentCount; ++k)
|
||||
{
|
||||
PDFBitReader::Value value = reader.read();
|
||||
color[k] = value * coefficient;
|
||||
PDFReal value = reader.read();
|
||||
|
||||
// Interpolate value, if it is not empty
|
||||
if (!decode.empty())
|
||||
{
|
||||
color[k] = interpolate(value, 0.0, max, decode[2 * k], decode[2 * k + 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
color[k] = value * coefficient;
|
||||
}
|
||||
}
|
||||
|
||||
QColor transformedColor = getColor(color);
|
||||
@ -147,19 +163,26 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const
|
||||
}
|
||||
|
||||
Q_ASSERT(componentCount > 0);
|
||||
std::vector<PDFInteger> colorKeyMask = imageData.getColorKeyMask();
|
||||
const std::vector<PDFInteger>& colorKeyMask = imageData.getColorKeyMask();
|
||||
if (colorKeyMask.size() / 2 != componentCount)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Invalid number of color components in color key mask. Expected %1, provided %2.").arg(2 * componentCount).arg(colorKeyMask.size()));
|
||||
}
|
||||
|
||||
const std::vector<PDFReal>& decode = imageData.getDecode();
|
||||
if (!decode.empty() && decode.size() != componentCount * 2)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Invalid size of the decoded array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size()));
|
||||
}
|
||||
|
||||
QDataStream stream(const_cast<QByteArray*>(&imageData.getData()), QIODevice::ReadOnly);
|
||||
PDFBitReader reader(&stream, imageData.getBitsPerComponent());
|
||||
|
||||
PDFColor color;
|
||||
color.resize(componentCount);
|
||||
|
||||
const double coefficient = 1.0 / reader.max();
|
||||
const double max = reader.max();
|
||||
const double coefficient = 1.0 / max;
|
||||
for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i)
|
||||
{
|
||||
reader.seek(i * imageData.getStride());
|
||||
@ -173,11 +196,20 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const
|
||||
for (unsigned int k = 0; k < componentCount; ++k)
|
||||
{
|
||||
PDFBitReader::Value value = reader.read();
|
||||
color[k] = value * coefficient;
|
||||
|
||||
// Interpolate value, if it is not empty
|
||||
if (!decode.empty())
|
||||
{
|
||||
color[k] = interpolate(value, 0.0, max, decode[2 * k], decode[2 * k + 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
color[k] = value * coefficient;
|
||||
}
|
||||
|
||||
Q_ASSERT(2 * k + 1 < colorKeyMask.size());
|
||||
if (static_cast<decltype(colorKeyMask)::value_type>(value) >= colorKeyMask[2 * k] &&
|
||||
static_cast<decltype(colorKeyMask)::value_type>(value) <= colorKeyMask[2 * k + 1])
|
||||
if (static_cast<std::decay<decltype(colorKeyMask)>::type::value_type>(value) >= colorKeyMask[2 * k] &&
|
||||
static_cast<std::decay<decltype(colorKeyMask)>::type::value_type>(value) <= colorKeyMask[2 * k + 1])
|
||||
{
|
||||
++maskedColors;
|
||||
}
|
||||
|
@ -80,8 +80,9 @@ public:
|
||||
enum class MaskingType
|
||||
{
|
||||
None,
|
||||
ColorKeyMasking,
|
||||
ImageMasking
|
||||
ColorKeyMasking, ///< Masking by color key
|
||||
Image, ///< Masking by image with alpha mask
|
||||
ImageMask, ///< Masking by 1-bit image (see "ImageMask" entry in image's dictionary), current color from the graphic state is used to paint an image
|
||||
};
|
||||
|
||||
explicit PDFImageData() :
|
||||
@ -102,7 +103,8 @@ public:
|
||||
unsigned int stride,
|
||||
MaskingType maskingType,
|
||||
QByteArray data,
|
||||
std::vector<PDFInteger>&& colorKeyMask) :
|
||||
std::vector<PDFInteger>&& colorKeyMask,
|
||||
std::vector<PDFReal>&& decode) :
|
||||
m_components(components),
|
||||
m_bitsPerComponent(bitsPerComponent),
|
||||
m_width(width),
|
||||
@ -110,7 +112,8 @@ public:
|
||||
m_stride(stride),
|
||||
m_maskingType(maskingType),
|
||||
m_data(qMove(data)),
|
||||
m_colorKeyMask(qMove(colorKeyMask))
|
||||
m_colorKeyMask(qMove(colorKeyMask)),
|
||||
m_decode(qMove(decode))
|
||||
{
|
||||
|
||||
}
|
||||
@ -122,7 +125,8 @@ public:
|
||||
unsigned int getStride() const { return m_stride; }
|
||||
MaskingType getMaskingType() const { return m_maskingType; }
|
||||
const QByteArray& getData() const { return m_data; }
|
||||
std::vector<PDFInteger> getColorKeyMask() const { return m_colorKeyMask; }
|
||||
const std::vector<PDFInteger>& getColorKeyMask() const { return m_colorKeyMask; }
|
||||
const std::vector<PDFReal>& getDecode() const { return m_decode; }
|
||||
|
||||
/// Returns number of color channels
|
||||
unsigned int getColorChannels() const { return m_components; }
|
||||
@ -149,6 +153,11 @@ private:
|
||||
/// If it is not empty, then it should contain 2 x number of color components,
|
||||
/// consisting of [ min_0, max_0, min_1, max_1, ... , min_n, max_n ].
|
||||
std::vector<PDFInteger> m_colorKeyMask;
|
||||
|
||||
/// Decode array. If it is empty, then no decoding is performed. If it is nonempty,
|
||||
/// then contains n pairs of numbers, where n is number of color components. If ImageMask
|
||||
/// in the image dictionary is true, then decode array should be [0 1] or [1 0].
|
||||
std::vector<PDFReal> m_decode;
|
||||
};
|
||||
|
||||
using PDFColor3 = std::array<PDFColorComponent, 3>;
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "pdfparser.h"
|
||||
#include "pdfdocument.h"
|
||||
#include "pdfexception.h"
|
||||
#include "pdfutils.h"
|
||||
|
||||
#include <stack>
|
||||
#include <iterator>
|
||||
|
@ -110,17 +110,6 @@ protected:
|
||||
/// \param value Value to be clamped
|
||||
inline PDFReal clampOutput(size_t index, PDFReal value) const { return qBound<PDFReal>(m_range[2 * index], value, m_range[2 * index + 1]); }
|
||||
|
||||
/// Performs linear mapping of value x in interval [x_min, x_max] to the interval [y_min, y_max].
|
||||
/// \param x Value to be linearly remapped from interval [x_min, x_max] to the interval [y_min, y_max].
|
||||
/// \param x_min Start of the input interval
|
||||
/// \param x_max End of the input interval
|
||||
/// \param y_min Start of the output interval
|
||||
/// \param y_max End of the output interval
|
||||
static inline constexpr PDFReal interpolate(PDFReal x, PDFReal x_min, PDFReal x_max, PDFReal y_min, PDFReal y_max)
|
||||
{
|
||||
return y_min + (x - x_min) * (y_max - y_min) / (x_max - x_min);
|
||||
}
|
||||
|
||||
/// Performs linear interpolation between c0 and c1 using x (in range [0.0, 1.0]). If x is not of this range,
|
||||
/// then the function succeeds, and returns value outside of interval [c0, c1].
|
||||
/// \param x Value to be interpolated
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "pdfdocument.h"
|
||||
#include "pdfconstants.h"
|
||||
#include "pdfexception.h"
|
||||
#include "pdfutils.h"
|
||||
|
||||
#include <openjpeg.h>
|
||||
#include <jpeglib.h>
|
||||
@ -57,12 +58,10 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
|
||||
throw PDFParserException(PDFTranslationContext::tr("Image has not data."));
|
||||
}
|
||||
|
||||
// TODO: Implement ImageMask
|
||||
// TODO: Implement Decode
|
||||
// TODO: Implement SMask
|
||||
// TODO: Implement SMaskInData
|
||||
|
||||
for (const char* notImplementedKey : { "ImageMask", "Decode", "SMask", "SMaskInData" })
|
||||
for (const char* notImplementedKey : { "SMask", "SMaskInData" })
|
||||
{
|
||||
if (dictionary->hasKey(notImplementedKey))
|
||||
{
|
||||
@ -72,6 +71,8 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
|
||||
|
||||
PDFImageData::MaskingType maskingType = PDFImageData::MaskingType::None;
|
||||
std::vector<PDFInteger> mask;
|
||||
std::vector<PDFReal> decode = loader.readNumberArrayFromDictionary(dictionary, "Decode");
|
||||
bool imageMask = loader.readBooleanFromDictionary(dictionary, "ImageMask", false);
|
||||
|
||||
// Fill Mask
|
||||
if (dictionary->hasKey("Mask"))
|
||||
@ -85,11 +86,16 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
|
||||
else if (object.isStream())
|
||||
{
|
||||
// TODO: Implement Mask Image
|
||||
maskingType = PDFImageData::MaskingType::ImageMasking;
|
||||
maskingType = PDFImageData::MaskingType::Image;
|
||||
throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Mask image is not implemented."));
|
||||
}
|
||||
}
|
||||
|
||||
if (imageMask)
|
||||
{
|
||||
maskingType = PDFImageData::MaskingType::ImageMask;
|
||||
}
|
||||
|
||||
// Retrieve filters
|
||||
PDFObject filters;
|
||||
if (dictionary->hasKey(PDF_STREAM_DICT_FILTER))
|
||||
@ -245,7 +251,7 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&codec);
|
||||
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, rowStride, maskingType, qMove(buffer), qMove(mask));
|
||||
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, rowStride, maskingType, qMove(buffer), qMove(mask), qMove(decode));
|
||||
}
|
||||
|
||||
jpeg_destroy_decompress(&codec);
|
||||
@ -411,7 +417,7 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
|
||||
}
|
||||
}
|
||||
|
||||
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, stride, maskingType, qMove(imageDataBuffer), qMove(mask));
|
||||
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, stride, maskingType, qMove(imageDataBuffer), qMove(mask), qMove(decode));
|
||||
valid = image.m_imageData.isValid();
|
||||
}
|
||||
else
|
||||
@ -477,7 +483,32 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
|
||||
const unsigned int stride = (components * bitsPerComponent * width + 7) / 8;
|
||||
|
||||
QByteArray imageDataBuffer = document->getDecodedStream(stream);
|
||||
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, stride, maskingType, qMove(imageDataBuffer), qMove(mask));
|
||||
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, stride, maskingType, qMove(imageDataBuffer), qMove(mask), qMove(decode));
|
||||
}
|
||||
else if (imageMask)
|
||||
{
|
||||
// We intentionally have 8 bits in the following code, because if ImageMask is set to true, then "BitsPerComponent"
|
||||
// should have always value of 1.
|
||||
const unsigned int bitsPerComponent = static_cast<unsigned int>(loader.readIntegerFromDictionary(dictionary, "BitsPerComponent", 8));
|
||||
|
||||
if (bitsPerComponent != 1)
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid number bits of image mask (should be 1 bit instead of %1 bits).").arg(bitsPerComponent));
|
||||
}
|
||||
|
||||
const unsigned int width = static_cast<unsigned int>(loader.readIntegerFromDictionary(dictionary, "Width", 0));
|
||||
const unsigned int height = static_cast<unsigned int>(loader.readIntegerFromDictionary(dictionary, "Height", 0));
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid size of image (%1x%2)").arg(width).arg(height));
|
||||
}
|
||||
|
||||
// Calculate stride
|
||||
const unsigned int stride = (width + 7) / 8;
|
||||
|
||||
QByteArray imageDataBuffer = document->getDecodedStream(stream);
|
||||
image.m_imageData = PDFImageData(1, bitsPerComponent, width, height, stride, maskingType, qMove(imageDataBuffer), qMove(mask), qMove(decode));
|
||||
}
|
||||
|
||||
return image;
|
||||
@ -489,6 +520,39 @@ QImage PDFImage::getImage() const
|
||||
{
|
||||
return m_colorSpace->getImage(m_imageData);
|
||||
}
|
||||
else if (m_imageData.getMaskingType() == PDFImageData::MaskingType::ImageMask)
|
||||
{
|
||||
if (m_imageData.getBitsPerComponent() != 1)
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid number bits of image mask (should be 1 bit instead of %1 bits).").arg(m_imageData.getBitsPerComponent()));
|
||||
}
|
||||
|
||||
if (m_imageData.getWidth() == 0 || m_imageData.getHeight() == 0)
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid size of image (%1x%2)").arg(m_imageData.getWidth()).arg(m_imageData.getHeight()));
|
||||
}
|
||||
|
||||
QImage image(m_imageData.getWidth(), m_imageData.getHeight(), QImage::Format_Alpha8);
|
||||
image.fill(QColor(Qt::transparent));
|
||||
|
||||
const bool flip01 = !m_imageData.getDecode().empty() && qFuzzyCompare(m_imageData.getDecode().front(), 1.0);
|
||||
QDataStream stream(const_cast<QByteArray*>(&m_imageData.getData()), QIODevice::ReadOnly);
|
||||
PDFBitReader reader(&stream, m_imageData.getBitsPerComponent());
|
||||
|
||||
for (unsigned int i = 0, rowCount = m_imageData.getHeight(); i < rowCount; ++i)
|
||||
{
|
||||
reader.seek(i * m_imageData.getStride());
|
||||
unsigned char* outputLine = image.scanLine(i);
|
||||
|
||||
for (unsigned int j = 0; j < m_imageData.getWidth(); ++j)
|
||||
{
|
||||
const bool transparent = flip01 != static_cast<bool>(reader.read());
|
||||
*outputLine++ = transparent ? 0x00 : 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
return QImage();
|
||||
}
|
||||
|
@ -99,6 +99,17 @@ private:
|
||||
Value m_bitsInBuffer;
|
||||
};
|
||||
|
||||
/// Performs linear mapping of value x in interval [x_min, x_max] to the interval [y_min, y_max].
|
||||
/// \param x Value to be linearly remapped from interval [x_min, x_max] to the interval [y_min, y_max].
|
||||
/// \param x_min Start of the input interval
|
||||
/// \param x_max End of the input interval
|
||||
/// \param y_min Start of the output interval
|
||||
/// \param y_max End of the output interval
|
||||
static inline constexpr PDFReal interpolate(PDFReal x, PDFReal x_min, PDFReal x_max, PDFReal y_min, PDFReal y_max)
|
||||
{
|
||||
return y_min + (x - x_min) * (y_max - y_min) / (x_max - x_min);
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
||||
#endif // PDFUTILS_H
|
||||
|
Loading…
x
Reference in New Issue
Block a user