Minor fixes of images

This commit is contained in:
Jakub Melka 2019-06-15 14:29:49 +02:00
parent 5896196f56
commit 630afbba61
5 changed files with 225 additions and 47 deletions

View File

@ -90,45 +90,117 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const
{ {
if (imageData.isValid()) if (imageData.isValid())
{ {
QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGB888); switch (imageData.getMaskingType())
image.fill(QColor(Qt::white));
// TODO: Implement images with bits different than 8
Q_ASSERT(imageData.getBitsPerComponent() == 8);
unsigned int componentCount = imageData.getComponents();
if (componentCount != getColorComponentCount())
{ {
throw PDFParserException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); case PDFImageData::MaskingType::None:
}
PDFColor color;
color.resize(componentCount);
for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i)
{
const unsigned char* rowData = imageData.getRow(i);
unsigned char* outputLine = image.scanLine(i);
for (unsigned int j = 0; j < imageData.getWidth(); ++j)
{ {
const unsigned char* currentData = rowData + (j * componentCount); QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGB888);
for (unsigned int k = 0; k < componentCount; ++k) image.fill(QColor(Qt::white));
unsigned int componentCount = imageData.getComponents();
if (componentCount != getColorComponentCount())
{ {
constexpr const double COEFFICIENT = 1.0 / 255.0; throw PDFParserException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount));
color[k] = currentData[k] * COEFFICIENT;
} }
QColor transformedColor = getColor(color); QDataStream stream(const_cast<QByteArray*>(&imageData.getData()), QIODevice::ReadOnly);
QRgb rgb = transformedColor.rgb(); PDFBitReader reader(&stream, imageData.getBitsPerComponent());
*outputLine++ = qRed(rgb); PDFColor color;
*outputLine++ = qGreen(rgb); color.resize(componentCount);
*outputLine++ = qBlue(rgb);
const double coefficient = 1.0 / reader.max();
for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i)
{
reader.seek(i * imageData.getStride());
unsigned char* outputLine = image.scanLine(i);
for (unsigned int j = 0; j < imageData.getWidth(); ++j)
{
for (unsigned int k = 0; k < componentCount; ++k)
{
PDFBitReader::Value value = reader.read();
color[k] = value * coefficient;
}
QColor transformedColor = getColor(color);
QRgb rgb = transformedColor.rgb();
*outputLine++ = qRed(rgb);
*outputLine++ = qGreen(rgb);
*outputLine++ = qBlue(rgb);
}
}
return image;
}
case PDFImageData::MaskingType::ColorKeyMasking:
{
QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGBA8888);
image.fill(QColor(Qt::transparent));
unsigned int componentCount = imageData.getComponents();
if (componentCount != getColorComponentCount())
{
throw PDFParserException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount));
}
Q_ASSERT(componentCount > 0);
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()));
}
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();
for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i)
{
reader.seek(i * imageData.getStride());
unsigned char* outputLine = image.scanLine(i);
for (unsigned int j = 0; j < imageData.getWidth(); ++j)
{
// Number of masked-out colors
unsigned int maskedColors = 0;
for (unsigned int k = 0; k < componentCount; ++k)
{
PDFBitReader::Value value = reader.read();
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])
{
++maskedColors;
}
}
QColor transformedColor = getColor(color);
QRgb rgb = transformedColor.rgb();
*outputLine++ = qRed(rgb);
*outputLine++ = qGreen(rgb);
*outputLine++ = qBlue(rgb);
*outputLine++ = (maskedColors == componentCount) ? 0x00 : 0xFF;
}
}
return image;
}
default:
{
throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Image masking not implemented!"));
} }
} }
return image;
} }
return QImage(); return QImage();

View File

@ -76,12 +76,21 @@ static constexpr const char* ICCBASED_RANGE = "Range";
class PDFImageData class PDFImageData
{ {
public: public:
enum class MaskingType
{
None,
ColorKeyMasking,
ImageMasking
};
explicit PDFImageData() : explicit PDFImageData() :
m_components(0), m_components(0),
m_bitsPerComponent(0), m_bitsPerComponent(0),
m_width(0), m_width(0),
m_height(0), m_height(0),
m_stride(0) m_stride(0),
m_maskingType(MaskingType::None)
{ {
} }
@ -91,13 +100,17 @@ public:
unsigned int width, unsigned int width,
unsigned int height, unsigned int height,
unsigned int stride, unsigned int stride,
QByteArray data) : MaskingType maskingType,
QByteArray data,
std::vector<PDFInteger>&& colorKeyMask) :
m_components(components), m_components(components),
m_bitsPerComponent(bitsPerComponent), m_bitsPerComponent(bitsPerComponent),
m_width(width), m_width(width),
m_height(height), m_height(height),
m_stride(stride), m_stride(stride),
m_data(qMove(data)) m_maskingType(maskingType),
m_data(qMove(data)),
m_colorKeyMask(qMove(colorKeyMask))
{ {
} }
@ -107,7 +120,9 @@ public:
unsigned int getWidth() const { return m_width; } unsigned int getWidth() const { return m_width; }
unsigned int getHeight() const { return m_height; } unsigned int getHeight() const { return m_height; }
unsigned int getStride() const { return m_stride; } unsigned int getStride() const { return m_stride; }
MaskingType getMaskingType() const { return m_maskingType; }
const QByteArray& getData() const { return m_data; } const QByteArray& getData() const { return m_data; }
std::vector<PDFInteger> getColorKeyMask() const { return m_colorKeyMask; }
/// Returns number of color channels /// Returns number of color channels
unsigned int getColorChannels() const { return m_components; } unsigned int getColorChannels() const { return m_components; }
@ -126,8 +141,14 @@ private:
unsigned int m_width; unsigned int m_width;
unsigned int m_height; unsigned int m_height;
unsigned int m_stride; unsigned int m_stride;
MaskingType m_maskingType;
QByteArray m_data; QByteArray m_data;
/// Mask entry of the image. If it is empty, no color key masking is induced.
/// 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;
}; };
using PDFColor3 = std::array<PDFColorComponent, 3>; using PDFColor3 = std::array<PDFColorComponent, 3>;

View File

@ -48,12 +48,6 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
PDFImage image; PDFImage image;
image.m_colorSpace = colorSpace; image.m_colorSpace = colorSpace;
// TODO: Implement ImageMask
// TODO: Implement Mask
// TODO: Implement Decode
// TODO: Implement SMask
// TODO: Implement SMaskInData
const PDFDictionary* dictionary = stream->getDictionary(); const PDFDictionary* dictionary = stream->getDictionary();
QByteArray content = document->getDecodedStream(stream); QByteArray content = document->getDecodedStream(stream);
PDFDocumentDataLoaderDecorator loader(document); PDFDocumentDataLoaderDecorator loader(document);
@ -63,6 +57,39 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
throw PDFParserException(PDFTranslationContext::tr("Image has not data.")); 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" })
{
if (dictionary->hasKey(notImplementedKey))
{
throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Not implemented image property '%2'.").arg(QString::fromLatin1(notImplementedKey)));
}
}
PDFImageData::MaskingType maskingType = PDFImageData::MaskingType::None;
std::vector<PDFInteger> mask;
// Fill Mask
if (dictionary->hasKey("Mask"))
{
const PDFObject& object = document->getObject(dictionary->get("Mask"));
if (object.isArray())
{
maskingType = PDFImageData::MaskingType::ColorKeyMasking;
mask = loader.readIntegerArray(object);
}
else if (object.isStream())
{
// TODO: Implement Mask Image
maskingType = PDFImageData::MaskingType::ImageMasking;
throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Mask image is not implemented."));
}
}
// Retrieve filters // Retrieve filters
PDFObject filters; PDFObject filters;
if (dictionary->hasKey(PDF_STREAM_DICT_FILTER)) if (dictionary->hasKey(PDF_STREAM_DICT_FILTER))
@ -218,7 +245,7 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
} }
jpeg_finish_decompress(&codec); jpeg_finish_decompress(&codec);
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, rowStride, qMove(buffer)); image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, rowStride, maskingType, qMove(buffer), qMove(mask));
} }
jpeg_destroy_decompress(&codec); jpeg_destroy_decompress(&codec);
@ -297,8 +324,6 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
} }
} }
} }
} }
} }
@ -386,15 +411,22 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
} }
} }
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, stride, qMove(imageDataBuffer)); image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, stride, maskingType, qMove(imageDataBuffer), qMove(mask));
valid = image.m_imageData.isValid();
} }
else else
{ {
// Easiest way is to // Easiest way is to just add errors to the error list
imageData.errors.push_back(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Incompatible color components for JPEG 2000 image."))); imageData.errors.push_back(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Incompatible color components for JPEG 2000 image.")));
} }
opj_image_destroy(jpegImage); opj_image_destroy(jpegImage);
if (valid)
{
// Image was successfully decoded
break;
}
} }
} }
@ -415,6 +447,38 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
} }
} }
} }
else if (imageFilterName == "CCITTFaxDecode")
{
throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Not implemented image filter 'CCITFaxDecode'."));
}
else if (imageFilterName == "JBIG2Decode")
{
throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Not implemented image filter 'JBIG2Decode'."));
}
else if (colorSpace)
{
// We treat data as binary maybe compressed stream (for example by Flate/LZW method), but data can also be not compressed.
const unsigned int components = static_cast<unsigned int>(colorSpace->getColorComponentCount());
const unsigned int bitsPerComponent = static_cast<unsigned int>(loader.readIntegerFromDictionary(dictionary, "BitsPerComponent", 8));
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 (bitsPerComponent < 1 || bitsPerComponent > 32)
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid number of bits per component (%1).").arg(bitsPerComponent));
}
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 = (components * bitsPerComponent * width + 7) / 8;
QByteArray imageDataBuffer = document->getDecodedStream(stream);
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, stride, maskingType, qMove(imageDataBuffer), qMove(mask));
}
return image; return image;
} }

View File

@ -36,6 +36,8 @@ namespace pdf
{ {
static constexpr const char* PDF_RESOURCE_EXTGSTATE = "ExtGState"; static constexpr const char* PDF_RESOURCE_EXTGSTATE = "ExtGState";
// TODO: Implement optional content groups
/// Process the contents of the page. /// Process the contents of the page.
class PDFPageContentProcessor : public PDFRenderErrorReporter class PDFPageContentProcessor : public PDFRenderErrorReporter
{ {

View File

@ -344,6 +344,13 @@ QByteArray PDFLzwDecodeFilter::apply(const QByteArray& data, const PDFDocument*
PDFDocumentDataLoaderDecorator loader(document); PDFDocumentDataLoaderDecorator loader(document);
early = loader.readInteger(dictionary->get("EarlyChange"), 1); early = loader.readInteger(dictionary->get("EarlyChange"), 1);
PDFInteger predictor = loader.readInteger(dictionary->get("Predictor"), 1);
if (predictor != 1)
{
// TODO: Implement Predictor algorithm
return QByteArray();
}
} }
PDFLzwStreamDecoder decoder(data, early); PDFLzwStreamDecoder decoder(data, early);
@ -352,8 +359,20 @@ QByteArray PDFLzwDecodeFilter::apply(const QByteArray& data, const PDFDocument*
QByteArray PDFFlateDecodeFilter::apply(const QByteArray& data, const PDFDocument* document, const PDFObject& parameters) const QByteArray PDFFlateDecodeFilter::apply(const QByteArray& data, const PDFDocument* document, const PDFObject& parameters) const
{ {
Q_UNUSED(document); const PDFObject& dereferencedParameters = document->getObject(parameters);
Q_UNUSED(parameters); if (dereferencedParameters.isDictionary())
{
const PDFDictionary* dictionary = dereferencedParameters.getDictionary();
PDFDocumentDataLoaderDecorator loader(document);
PDFInteger predictor = loader.readInteger(dictionary->get("Predictor"), 1);
if (predictor != 1)
{
// TODO: Implement Predictor algorithm
return QByteArray();
}
}
uint32_t size = data.size(); uint32_t size = data.size();