From 630afbba61cfd1bef3e35a043d33cabab05ad7ad Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 15 Jun 2019 14:29:49 +0200 Subject: [PATCH] Minor fixes of images --- PdfForQtLib/sources/pdfcolorspaces.cpp | 134 ++++++++++++++---- PdfForQtLib/sources/pdfcolorspaces.h | 27 +++- PdfForQtLib/sources/pdfimage.cpp | 86 +++++++++-- PdfForQtLib/sources/pdfpagecontentprocessor.h | 2 + PdfForQtLib/sources/pdfstreamfilters.cpp | 23 ++- 5 files changed, 225 insertions(+), 47 deletions(-) diff --git a/PdfForQtLib/sources/pdfcolorspaces.cpp b/PdfForQtLib/sources/pdfcolorspaces.cpp index 8368a5b..c155d2e 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.cpp +++ b/PdfForQtLib/sources/pdfcolorspaces.cpp @@ -90,45 +90,117 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const { if (imageData.isValid()) { - 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()) + switch (imageData.getMaskingType()) { - throw PDFParserException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); - } - - 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) + case PDFImageData::MaskingType::None: { - const unsigned char* currentData = rowData + (j * componentCount); - for (unsigned int k = 0; k < componentCount; ++k) + QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGB888); + image.fill(QColor(Qt::white)); + + unsigned int componentCount = imageData.getComponents(); + if (componentCount != getColorComponentCount()) { - constexpr const double COEFFICIENT = 1.0 / 255.0; - color[k] = currentData[k] * COEFFICIENT; + throw PDFParserException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); } - QColor transformedColor = getColor(color); - QRgb rgb = transformedColor.rgb(); + QDataStream stream(const_cast(&imageData.getData()), QIODevice::ReadOnly); + PDFBitReader reader(&stream, imageData.getBitsPerComponent()); - *outputLine++ = qRed(rgb); - *outputLine++ = qGreen(rgb); - *outputLine++ = qBlue(rgb); + 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) + { + 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 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(&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(value) >= colorKeyMask[2 * k] && + static_cast(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(); diff --git a/PdfForQtLib/sources/pdfcolorspaces.h b/PdfForQtLib/sources/pdfcolorspaces.h index 6d4a858..6908bdd 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.h +++ b/PdfForQtLib/sources/pdfcolorspaces.h @@ -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&& 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 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 m_colorKeyMask; }; using PDFColor3 = std::array; diff --git a/PdfForQtLib/sources/pdfimage.cpp b/PdfForQtLib/sources/pdfimage.cpp index 40099fc..3b44ae0 100644 --- a/PdfForQtLib/sources/pdfimage.cpp +++ b/PdfForQtLib/sources/pdfimage.cpp @@ -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 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(colorSpace->getColorComponentCount()); + const unsigned int bitsPerComponent = static_cast(loader.readIntegerFromDictionary(dictionary, "BitsPerComponent", 8)); + const unsigned int width = static_cast(loader.readIntegerFromDictionary(dictionary, "Width", 0)); + const unsigned int height = static_cast(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; } diff --git a/PdfForQtLib/sources/pdfpagecontentprocessor.h b/PdfForQtLib/sources/pdfpagecontentprocessor.h index d2ba83c..3c74096 100644 --- a/PdfForQtLib/sources/pdfpagecontentprocessor.h +++ b/PdfForQtLib/sources/pdfpagecontentprocessor.h @@ -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 { diff --git a/PdfForQtLib/sources/pdfstreamfilters.cpp b/PdfForQtLib/sources/pdfstreamfilters.cpp index 5483f3f..97fae4e 100644 --- a/PdfForQtLib/sources/pdfstreamfilters.cpp +++ b/PdfForQtLib/sources/pdfstreamfilters.cpp @@ -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();