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

@ -89,34 +89,38 @@ size_t PDFDeviceCMYKColorSpace::getColorComponentCount() const
QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const
{
if (imageData.isValid())
{
switch (imageData.getMaskingType())
{
case PDFImageData::MaskingType::None:
{
QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGB888);
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));
}
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)
{
const unsigned char* rowData = imageData.getRow(i);
reader.seek(i * imageData.getStride());
unsigned char* outputLine = image.scanLine(i);
for (unsigned int j = 0; j < imageData.getWidth(); ++j)
{
const unsigned char* currentData = rowData + (j * componentCount);
for (unsigned int k = 0; k < componentCount; ++k)
{
constexpr const double COEFFICIENT = 1.0 / 255.0;
color[k] = currentData[k] * COEFFICIENT;
PDFBitReader::Value value = reader.read();
color[k] = value * coefficient;
}
QColor transformedColor = getColor(color);
@ -131,6 +135,74 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const
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 QImage();
}

View File

@ -76,12 +76,21 @@ static constexpr const char* ICCBASED_RANGE = "Range";
class PDFImageData
{
public:
enum class MaskingType
{
None,
ColorKeyMasking,
ImageMasking
};
explicit PDFImageData() :
m_components(0),
m_bitsPerComponent(0),
m_width(0),
m_height(0),
m_stride(0)
m_stride(0),
m_maskingType(MaskingType::None)
{
}
@ -91,13 +100,17 @@ public:
unsigned int width,
unsigned int height,
unsigned int stride,
QByteArray data) :
MaskingType maskingType,
QByteArray data,
std::vector<PDFInteger>&& colorKeyMask) :
m_components(components),
m_bitsPerComponent(bitsPerComponent),
m_width(width),
m_height(height),
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 getHeight() const { return m_height; }
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; }
/// Returns number of color channels
unsigned int getColorChannels() const { return m_components; }
@ -126,8 +141,14 @@ private:
unsigned int m_width;
unsigned int m_height;
unsigned int m_stride;
MaskingType m_maskingType;
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>;

View File

@ -48,12 +48,6 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
PDFImage image;
image.m_colorSpace = colorSpace;
// TODO: Implement ImageMask
// TODO: Implement Mask
// TODO: Implement Decode
// TODO: Implement SMask
// TODO: Implement SMaskInData
const PDFDictionary* dictionary = stream->getDictionary();
QByteArray content = document->getDecodedStream(stream);
PDFDocumentDataLoaderDecorator loader(document);
@ -63,6 +57,39 @@ 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" })
{
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
PDFObject filters;
if (dictionary->hasKey(PDF_STREAM_DICT_FILTER))
@ -218,7 +245,7 @@ PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* str
}
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);
@ -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
{
// 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.")));
}
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;
}

View File

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

View File

@ -344,6 +344,13 @@ QByteArray PDFLzwDecodeFilter::apply(const QByteArray& data, const PDFDocument*
PDFDocumentDataLoaderDecorator loader(document);
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);
@ -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
{
Q_UNUSED(document);
Q_UNUSED(parameters);
const PDFObject& dereferencedParameters = document->getObject(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();