mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Images (just beginning)
This commit is contained in:
@@ -85,6 +85,54 @@ size_t PDFDeviceCMYKColorSpace::getColorComponentCount() const
|
||||
return 4;
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
QColor transformedColor = getColor(color);
|
||||
QRgb rgb = transformedColor.rgb();
|
||||
|
||||
*outputLine++ = qRed(rgb);
|
||||
*outputLine++ = qGreen(rgb);
|
||||
*outputLine++ = qBlue(rgb);
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
return QImage();
|
||||
}
|
||||
|
||||
PDFColorSpacePointer PDFAbstractColorSpace::createColorSpace(const PDFDictionary* colorSpaceDictionary,
|
||||
const PDFDocument* document,
|
||||
const PDFObject& colorSpace)
|
||||
@@ -708,4 +756,12 @@ PDFColorSpacePointer PDFSeparationColorSpace::createSeparationColorSpace(const P
|
||||
return PDFColorSpacePointer(new PDFSeparationColorSpace(qMove(colorName), qMove(alternateColorSpace), qMove(tintTransform)));
|
||||
}
|
||||
|
||||
const unsigned char* PDFImageData::getRow(unsigned int rowIndex) const
|
||||
{
|
||||
const unsigned char* data = reinterpret_cast<const unsigned char*>(m_data.constData());
|
||||
|
||||
Q_ASSERT(rowIndex < m_height);
|
||||
return data + (rowIndex * m_stride);
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2019 Jakub Melka
|
||||
// Copyright (C) 2019 Jakub Melka
|
||||
//
|
||||
// This file is part of PdfForQt.
|
||||
//
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "pdffunction.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QImage>
|
||||
#include <QSharedPointer>
|
||||
|
||||
namespace pdf
|
||||
@@ -70,6 +71,57 @@ static constexpr const char* ICCBASED_ALTERNATE = "Alternate";
|
||||
static constexpr const char* ICCBASED_N = "N";
|
||||
static constexpr const char* ICCBASED_RANGE = "Range";
|
||||
|
||||
/// Image raw data - containing data for image. Image data are row-ordered, and by components.
|
||||
/// So the row can be for 3-components RGB like 'RGBRGBRGB...RGB', where size of row in bytes is 3 * width of image.
|
||||
class PDFImageData
|
||||
{
|
||||
public:
|
||||
explicit PDFImageData() :
|
||||
m_components(0),
|
||||
m_bitsPerComponent(0),
|
||||
m_width(0),
|
||||
m_height(0),
|
||||
m_stride(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
explicit inline PDFImageData(unsigned int components,
|
||||
unsigned int bitsPerComponent,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
unsigned int stride,
|
||||
QByteArray data) :
|
||||
m_components(components),
|
||||
m_bitsPerComponent(bitsPerComponent),
|
||||
m_width(width),
|
||||
m_height(height),
|
||||
m_stride(stride),
|
||||
m_data(qMove(data))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
unsigned int getComponents() const { return m_components; }
|
||||
unsigned int getBitsPerComponent() const { return m_bitsPerComponent; }
|
||||
unsigned int getWidth() const { return m_width; }
|
||||
unsigned int getHeight() const { return m_height; }
|
||||
unsigned int getStride() const { return m_stride; }
|
||||
|
||||
bool isValid() const { return m_width && m_height && m_components && m_bitsPerComponent; }
|
||||
|
||||
const unsigned char* getRow(unsigned int rowIndex) const;
|
||||
|
||||
private:
|
||||
unsigned int m_components;
|
||||
unsigned int m_bitsPerComponent;
|
||||
unsigned int m_width;
|
||||
unsigned int m_height;
|
||||
unsigned int m_stride;
|
||||
|
||||
QByteArray m_data;
|
||||
};
|
||||
|
||||
using PDFColor3 = std::array<PDFColorComponent, 3>;
|
||||
|
||||
/// Matrix for color component multiplication (for example, conversion between some color spaces)
|
||||
@@ -117,6 +169,7 @@ public:
|
||||
virtual QColor getDefaultColor() const = 0;
|
||||
virtual QColor getColor(const PDFColor& color) const = 0;
|
||||
virtual size_t getColorComponentCount() const = 0;
|
||||
virtual QImage getImage(const PDFImageData& imageData) const;
|
||||
|
||||
/// Parses the desired color space. If desired color space is not found, then exception is thrown.
|
||||
/// If everything is OK, then shared pointer to the new color space is returned.
|
||||
|
@@ -430,7 +430,7 @@ void PDFRealizedFontImpl::fillTextSequence(const QByteArray& byteArray, TextSequ
|
||||
if (!glyphIndex)
|
||||
{
|
||||
// Try to obtain glyph index from unicode
|
||||
if (m_face->charmap->encoding == FT_ENCODING_UNICODE)
|
||||
if (m_face->charmap && m_face->charmap->encoding == FT_ENCODING_UNICODE)
|
||||
{
|
||||
glyphIndex = FT_Get_Char_Index(m_face, (*encoding)[static_cast<uint8_t>(byteArray[i])].unicode());
|
||||
}
|
||||
|
356
PdfForQtLib/sources/pdfimage.cpp
Normal file
356
PdfForQtLib/sources/pdfimage.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
// Copyright (C) 2019 Jakub Melka
|
||||
//
|
||||
// This file is part of PdfForQt.
|
||||
//
|
||||
// PdfForQt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// PdfForQt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "pdfimage.h"
|
||||
#include "pdfdocument.h"
|
||||
#include "pdfconstants.h"
|
||||
#include "pdfexception.h"
|
||||
|
||||
#include <openjpeg.h>
|
||||
#include <jpeglib.h>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
struct PDFJPEG2000ImageData
|
||||
{
|
||||
const QByteArray* byteArray = nullptr;
|
||||
OPJ_SIZE_T position = 0;
|
||||
|
||||
static OPJ_SIZE_T read(void* p_buffer, OPJ_SIZE_T p_nb_bytes, void* p_user_data);
|
||||
static OPJ_BOOL seek(OPJ_OFF_T p_nb_bytes, void* p_user_data);
|
||||
static OPJ_OFF_T skip(OPJ_OFF_T p_nb_bytes, void* p_user_data);
|
||||
};
|
||||
|
||||
struct PDFJPEGDCTSource
|
||||
{
|
||||
jpeg_source_mgr sourceManager;
|
||||
const QByteArray* buffer = nullptr;
|
||||
};
|
||||
|
||||
PDFImage PDFImage::createImage(const PDFDocument* document, const PDFStream* stream, PDFColorSpacePointer colorSpace)
|
||||
{
|
||||
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);
|
||||
|
||||
if (content.isEmpty())
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Image has not data."));
|
||||
}
|
||||
|
||||
// Retrieve filters
|
||||
PDFObject filters;
|
||||
if (dictionary->hasKey(PDF_STREAM_DICT_FILTER))
|
||||
{
|
||||
filters = document->getObject(dictionary->get(PDF_STREAM_DICT_FILTER));
|
||||
}
|
||||
else if (dictionary->hasKey(PDF_STREAM_DICT_FILE_FILTER))
|
||||
{
|
||||
filters = document->getObject(dictionary->get(PDF_STREAM_DICT_FILE_FILTER));
|
||||
}
|
||||
|
||||
// Retrieve filter parameters
|
||||
PDFObject filterParameters;
|
||||
if (dictionary->hasKey(PDF_STREAM_DICT_DECODE_PARMS))
|
||||
{
|
||||
filterParameters = document->getObject(dictionary->get(PDF_STREAM_DICT_DECODE_PARMS));
|
||||
}
|
||||
else if (dictionary->hasKey(PDF_STREAM_DICT_FDECODE_PARMS))
|
||||
{
|
||||
filterParameters = document->getObject(dictionary->get(PDF_STREAM_DICT_FDECODE_PARMS));
|
||||
}
|
||||
|
||||
QByteArray imageFilterName;
|
||||
if (filters.isName())
|
||||
{
|
||||
imageFilterName = filters.getString();
|
||||
}
|
||||
else if (filters.isArray())
|
||||
{
|
||||
const PDFArray* filterArray = filters.getArray();
|
||||
const size_t filterCount = filterArray->getCount();
|
||||
|
||||
if (filterCount)
|
||||
{
|
||||
const PDFObject& object = document->getObject(filterArray->getItem(filterCount - 1));
|
||||
if (object.isName())
|
||||
{
|
||||
imageFilterName = object.getString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (imageFilterName == "DCTDecode" || imageFilterName == "DCT")
|
||||
{
|
||||
// TODO: Check, if mutex is needed
|
||||
// Used library is not thread safe. We must use a mutex!
|
||||
static QMutex mutex;
|
||||
QMutexLocker lock(&mutex);
|
||||
|
||||
int colorTransform = loader.readIntegerFromDictionary(dictionary, "ColorTransform", -1);
|
||||
|
||||
jpeg_decompress_struct codec;
|
||||
jpeg_error_mgr errorManager;
|
||||
std::memset(&codec, 0, sizeof(jpeg_decompress_struct));
|
||||
std::memset(&errorManager, 0, sizeof(errorManager));
|
||||
|
||||
PDFJPEGDCTSource source;
|
||||
source.buffer = &content;
|
||||
std::memset(&source.sourceManager, 0, sizeof(jpeg_source_mgr));
|
||||
|
||||
auto errorMethod = [](j_common_ptr ptr)
|
||||
{
|
||||
char buffer[JMSG_LENGTH_MAX] = { };
|
||||
(ptr->err->format_message)(ptr, buffer);
|
||||
|
||||
jpeg_destroy(ptr);
|
||||
throw PDFParserException(PDFTranslationContext::tr("Error reading JPEG (DCT) image: %1.").arg(QString::fromLatin1(buffer)));
|
||||
};
|
||||
|
||||
auto fillInputBufferMethod = [](j_decompress_ptr decompress) -> boolean
|
||||
{
|
||||
PDFJPEGDCTSource* source = reinterpret_cast<PDFJPEGDCTSource*>(decompress->src);
|
||||
|
||||
if (!source->sourceManager.next_input_byte)
|
||||
{
|
||||
const QByteArray* buffer = source->buffer;
|
||||
source->sourceManager.next_input_byte = reinterpret_cast<const JOCTET*>(buffer->constData());
|
||||
source->sourceManager.bytes_in_buffer = buffer->size();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
};
|
||||
|
||||
auto skipInputDataMethod = [](j_decompress_ptr decompress, long num_bytes)
|
||||
{
|
||||
PDFJPEGDCTSource* source = reinterpret_cast<PDFJPEGDCTSource*>(decompress->src);
|
||||
|
||||
const size_t skippedBytes = qMin(source->sourceManager.bytes_in_buffer, static_cast<size_t>(num_bytes));
|
||||
source->sourceManager.next_input_byte += skippedBytes;
|
||||
source->sourceManager.bytes_in_buffer -= skippedBytes;
|
||||
};
|
||||
|
||||
source.sourceManager.bytes_in_buffer = 0;
|
||||
source.sourceManager.next_input_byte = nullptr;
|
||||
source.sourceManager.init_source = [](j_decompress_ptr) { };
|
||||
source.sourceManager.fill_input_buffer = fillInputBufferMethod;
|
||||
source.sourceManager.skip_input_data = skipInputDataMethod;
|
||||
source.sourceManager.resync_to_restart = jpeg_resync_to_restart;
|
||||
source.sourceManager.term_source = [](j_decompress_ptr) { };
|
||||
|
||||
jpeg_std_error(&errorManager);
|
||||
errorManager.error_exit = errorMethod;
|
||||
codec.err = &errorManager;
|
||||
|
||||
jpeg_create_decompress(&codec);
|
||||
codec.src = reinterpret_cast<jpeg_source_mgr*>(&source);
|
||||
|
||||
if (jpeg_read_header(&codec, TRUE) == JPEG_HEADER_OK)
|
||||
{
|
||||
// Determine color transform
|
||||
if (colorTransform == -1 && codec.saw_Adobe_marker)
|
||||
{
|
||||
colorTransform = codec.Adobe_transform;
|
||||
}
|
||||
|
||||
// Set the input transform
|
||||
if (colorTransform > -1)
|
||||
{
|
||||
switch (codec.num_components)
|
||||
{
|
||||
case 3:
|
||||
{
|
||||
codec.jpeg_color_space = colorTransform ? JCS_YCbCr : JCS_RGB;
|
||||
break;
|
||||
}
|
||||
|
||||
case 4:
|
||||
{
|
||||
codec.jpeg_color_space = colorTransform ? JCS_YCCK : JCS_CMYK;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jpeg_start_decompress(&codec);
|
||||
|
||||
const JDIMENSION rowStride = codec.output_width * codec.output_components;
|
||||
JSAMPARRAY samples = codec.mem->alloc_sarray(reinterpret_cast<j_common_ptr>(&codec), JPOOL_IMAGE, rowStride, 1);
|
||||
JDIMENSION scanLineCount = codec.output_height;
|
||||
|
||||
const unsigned int width = codec.output_width;
|
||||
const unsigned int height = codec.output_height;
|
||||
const unsigned int components = codec.output_components;
|
||||
const unsigned int bitsPerComponent = 8;
|
||||
QByteArray buffer(rowStride * height, 0);
|
||||
JSAMPROW rowData = reinterpret_cast<JSAMPROW>(buffer.data());
|
||||
|
||||
while (scanLineCount)
|
||||
{
|
||||
JDIMENSION readCount = jpeg_read_scanlines(&codec, samples, 1);
|
||||
std::memcpy(rowData, samples[0], rowStride);
|
||||
scanLineCount -= readCount;
|
||||
rowData += rowStride;
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&codec);
|
||||
image.m_imageData = PDFImageData(components, bitsPerComponent, width, height, rowStride, qMove(buffer));
|
||||
}
|
||||
|
||||
jpeg_destroy_decompress(&codec);
|
||||
}
|
||||
else if (imageFilterName == "JPXDecode")
|
||||
{
|
||||
PDFJPEG2000ImageData imageData;
|
||||
imageData.byteArray = &content;
|
||||
imageData.position = 0;
|
||||
|
||||
opj_stream_t* stream = opj_stream_default_create(OPJ_TRUE);
|
||||
opj_stream_set_user_data(stream, &imageData, nullptr);
|
||||
opj_stream_set_user_data_length(stream, sizeof(PDFJPEG2000ImageData));
|
||||
opj_stream_set_read_function(stream, &PDFJPEG2000ImageData::read);
|
||||
opj_stream_set_seek_function(stream, &PDFJPEG2000ImageData::seek);
|
||||
opj_stream_set_skip_function(stream, &PDFJPEG2000ImageData::skip);
|
||||
|
||||
opj_dparameters_t decompressParameters;
|
||||
opj_set_default_decoder_parameters(&decompressParameters);
|
||||
|
||||
CODEC_FORMAT formats[] = { OPJ_CODEC_J2K, OPJ_CODEC_JPT, OPJ_CODEC_JP2, OPJ_CODEC_JPP, OPJ_CODEC_JPX };
|
||||
for (CODEC_FORMAT format : formats)
|
||||
{
|
||||
opj_codec_t* codec = opj_create_decompress(format);
|
||||
|
||||
if (!codec)
|
||||
{
|
||||
// Codec is not present
|
||||
continue;
|
||||
}
|
||||
|
||||
// Setup the decoder
|
||||
opj_setup_decoder(codec, &decompressParameters);
|
||||
|
||||
// Try to read the header
|
||||
opj_image_t* image = nullptr;
|
||||
if (opj_read_header(stream, codec, &image))
|
||||
{
|
||||
if (opj_set_decode_area(codec, image, decompressParameters.DA_x0, decompressParameters.DA_y0, decompressParameters.DA_x1, decompressParameters.DA_y1))
|
||||
{
|
||||
if (opj_decode(codec, stream, image))
|
||||
{
|
||||
if (opj_end_decompress(codec, stream))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opj_destroy_codec(codec);
|
||||
}
|
||||
|
||||
opj_stream_destroy(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
QImage PDFImage::getImage() const
|
||||
{
|
||||
if (m_colorSpace)
|
||||
{
|
||||
return m_colorSpace->getImage(m_imageData);
|
||||
}
|
||||
|
||||
return QImage();
|
||||
}
|
||||
|
||||
OPJ_SIZE_T PDFJPEG2000ImageData::read(void* p_buffer, OPJ_SIZE_T p_nb_bytes, void* p_user_data)
|
||||
{
|
||||
PDFJPEG2000ImageData* data = reinterpret_cast<PDFJPEG2000ImageData*>(p_user_data);
|
||||
|
||||
// Remaining length
|
||||
OPJ_OFF_T length = static_cast<OPJ_OFF_T>(data->byteArray->size()) - data->position;
|
||||
|
||||
if (length < 0)
|
||||
{
|
||||
length = 0;
|
||||
}
|
||||
|
||||
if (length > static_cast<OPJ_OFF_T>(p_nb_bytes))
|
||||
{
|
||||
length = static_cast<OPJ_OFF_T>(p_nb_bytes);
|
||||
}
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
std::memcpy(p_buffer, data->byteArray->constData() + data->position, length);
|
||||
data->position += length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
OPJ_BOOL PDFJPEG2000ImageData::seek(OPJ_OFF_T p_nb_bytes, void* p_user_data)
|
||||
{
|
||||
PDFJPEG2000ImageData* data = reinterpret_cast<PDFJPEG2000ImageData*>(p_user_data);
|
||||
|
||||
if (p_nb_bytes >= data->byteArray->size())
|
||||
{
|
||||
return OPJ_FALSE;
|
||||
}
|
||||
|
||||
data->position = p_nb_bytes;
|
||||
return OPJ_TRUE;
|
||||
}
|
||||
|
||||
OPJ_OFF_T PDFJPEG2000ImageData::skip(OPJ_OFF_T p_nb_bytes, void* p_user_data)
|
||||
{
|
||||
PDFJPEG2000ImageData* data = reinterpret_cast<PDFJPEG2000ImageData*>(p_user_data);
|
||||
|
||||
// Remaining length
|
||||
OPJ_OFF_T length = static_cast<OPJ_OFF_T>(data->byteArray->size()) - data->position;
|
||||
|
||||
if (length < 0)
|
||||
{
|
||||
length = 0;
|
||||
}
|
||||
|
||||
if (length > static_cast<OPJ_OFF_T>(p_nb_bytes))
|
||||
{
|
||||
length = static_cast<OPJ_OFF_T>(p_nb_bytes);
|
||||
}
|
||||
|
||||
data->position += length;
|
||||
return length;
|
||||
}
|
||||
|
||||
} // namespace pdf
|
54
PdfForQtLib/sources/pdfimage.h
Normal file
54
PdfForQtLib/sources/pdfimage.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (C) 2019 Jakub Melka
|
||||
//
|
||||
// This file is part of PdfForQt.
|
||||
//
|
||||
// PdfForQt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// PdfForQt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef PDFIMAGE_H
|
||||
#define PDFIMAGE_H
|
||||
|
||||
#include "pdfcolorspaces.h"
|
||||
|
||||
#include <QByteArray>
|
||||
|
||||
class QByteArray;
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
class PDFStream;
|
||||
class PDFDocument;
|
||||
|
||||
class PDFImage
|
||||
{
|
||||
public:
|
||||
|
||||
/// Creates image from the content and the dictionary. If image can't be created, then exception is thrown.
|
||||
/// \param document Document
|
||||
/// \param stream Stream with image
|
||||
/// \param colorSpace Color space of the image
|
||||
static PDFImage createImage(const PDFDocument* document, const PDFStream* stream, PDFColorSpacePointer colorSpace);
|
||||
|
||||
/// Returns image transformed from image data and color space
|
||||
QImage getImage() const;
|
||||
|
||||
private:
|
||||
PDFImage() = default;
|
||||
|
||||
PDFImageData m_imageData;
|
||||
PDFColorSpacePointer m_colorSpace;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
||||
#endif // PDFIMAGE_H
|
@@ -18,6 +18,7 @@
|
||||
#include "pdfpagecontentprocessor.h"
|
||||
#include "pdfdocument.h"
|
||||
#include "pdfexception.h"
|
||||
#include "pdfimage.h"
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
@@ -156,6 +157,7 @@ PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page, const PDFD
|
||||
m_fontCache(fontCache),
|
||||
m_colorSpaceDictionary(nullptr),
|
||||
m_fontDictionary(nullptr),
|
||||
m_xobjectDictionary(nullptr),
|
||||
m_textBeginEndState(0)
|
||||
{
|
||||
Q_ASSERT(page);
|
||||
@@ -178,6 +180,7 @@ PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page, const PDFD
|
||||
|
||||
m_colorSpaceDictionary = getDictionary(COLOR_SPACE_DICTIONARY);
|
||||
m_fontDictionary = getDictionary("Font");
|
||||
m_xobjectDictionary = getDictionary("XObject");
|
||||
}
|
||||
|
||||
PDFPageContentProcessor::~PDFPageContentProcessor()
|
||||
@@ -277,6 +280,11 @@ void PDFPageContentProcessor::performClipping(const QPainterPath& path, Qt::Fill
|
||||
Q_UNUSED(fillRule);
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::performImagePainting(const QImage& image)
|
||||
{
|
||||
Q_UNUSED(image);
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::performUpdateGraphicsState(const PDFPageContentProcessorState& state)
|
||||
{
|
||||
if (state.getStateFlags().testFlag(PDFPageContentProcessorState::StateTextFont) ||
|
||||
@@ -738,6 +746,13 @@ void PDFPageContentProcessor::processCommand(const QByteArray& command)
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::PaintXObject:
|
||||
{
|
||||
// Do, paint the X Object (image, form, ...)
|
||||
invokeOperator(&PDFPageContentProcessor::operatorPaintXObject);
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::Invalid:
|
||||
{
|
||||
m_errorList.append(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Unknown operator '%1'.").arg(QString::fromLatin1(command))));
|
||||
@@ -1752,6 +1767,64 @@ void PDFPageContentProcessor::operatorTextSetSpacingAndShowText(PDFReal t_w, PDF
|
||||
operatorTextNextLineShowText(qMove(text));
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::operatorPaintXObject(PDFPageContentProcessor::PDFOperandName name)
|
||||
{
|
||||
if (m_xobjectDictionary)
|
||||
{
|
||||
const PDFObject& object = m_document->getObject(m_xobjectDictionary->get(name.name));
|
||||
if (object.isStream())
|
||||
{
|
||||
const PDFStream* stream = object.getStream();
|
||||
const PDFDictionary* streamDictionary = stream->getDictionary();
|
||||
|
||||
PDFDocumentDataLoaderDecorator loader(m_document);
|
||||
QByteArray subtype = loader.readNameFromDictionary(streamDictionary, "Subtype");
|
||||
if (subtype == "Image")
|
||||
{
|
||||
PDFColorSpacePointer colorSpace;
|
||||
|
||||
if (streamDictionary->hasKey("ColorSpace"))
|
||||
{
|
||||
const PDFObject& colorSpaceObject = m_document->getObject(streamDictionary->get("ColorSpace"));
|
||||
if (colorSpaceObject.isName() || colorSpaceObject.isArray())
|
||||
{
|
||||
colorSpace = PDFAbstractColorSpace::createColorSpace(m_colorSpaceDictionary, m_document, colorSpaceObject);
|
||||
}
|
||||
else if (!colorSpaceObject.isNull())
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid color space of the image."));
|
||||
}
|
||||
}
|
||||
|
||||
PDFImage pdfImage = PDFImage::createImage(m_document, stream, qMove(colorSpace));
|
||||
QImage image = pdfImage.getImage();
|
||||
|
||||
if (!image.isNull())
|
||||
{
|
||||
performImagePainting(image);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't decode the image."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Handle another XObjects
|
||||
throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Unknown XObject type '%1'.").arg(QString::fromLatin1(subtype)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid format of XObject. Dictionary expected."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("XObject resource dictionary not found."));
|
||||
}
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::drawText(const TextSequence& textSequence)
|
||||
{
|
||||
if (textSequence.items.empty())
|
||||
|
@@ -355,6 +355,11 @@ protected:
|
||||
/// clip along the path (intersect with current clipping path).
|
||||
virtual void performClipping(const QPainterPath& path, Qt::FillRule fillRule);
|
||||
|
||||
/// This function has to be implemented in the client drawing implementation, it should
|
||||
/// draw the image.
|
||||
/// \param image Image to be painted
|
||||
virtual void performImagePainting(const QImage& image);
|
||||
|
||||
/// This function has to be implemented in the client drawing implementation, it should
|
||||
/// update the device according to the graphic state change. The flags are set when
|
||||
/// the value differs from the previous graphic state.
|
||||
@@ -554,6 +559,9 @@ private:
|
||||
void operatorTextNextLineShowText(PDFOperandString text); ///< ', move to the next line and show text ("string '" is equivalent to "T* string Tj")
|
||||
void operatorTextSetSpacingAndShowText(PDFReal t_w, PDFReal t_c, PDFOperandString text); ///< ", move to the next line, set spacing and show text (equivalent to sequence "w1 Tw w2 Tc string '")
|
||||
|
||||
// XObject: Do
|
||||
void operatorPaintXObject(PDFOperandName name); ///< Do, paint the X Object (image, form, ...)
|
||||
|
||||
// Draws the text using the text sequence
|
||||
void drawText(const TextSequence& textSequence);
|
||||
|
||||
@@ -574,6 +582,7 @@ private:
|
||||
const PDFFontCache* m_fontCache;
|
||||
const PDFDictionary* m_colorSpaceDictionary;
|
||||
const PDFDictionary* m_fontDictionary;
|
||||
const PDFDictionary* m_xobjectDictionary;
|
||||
|
||||
// Default color spaces
|
||||
PDFColorSpacePointer m_deviceGrayColorSpace;
|
||||
|
@@ -82,6 +82,25 @@ void PDFPainter::performClipping(const QPainterPath& path, Qt::FillRule fillRule
|
||||
m_painter->setClipPath(path, Qt::IntersectClip);
|
||||
}
|
||||
|
||||
void PDFPainter::performImagePainting(const QImage& image)
|
||||
{
|
||||
m_painter->save();
|
||||
|
||||
// TODO: Draw smooth images
|
||||
QMatrix imageTransform(1.0 / image.width(), 0, 0, 1.0 / image.height(), 0, 0);
|
||||
QMatrix worldMatrix = imageTransform * m_painter->worldMatrix();
|
||||
|
||||
// Because Qt uses opposite axis direction than PDF, then we must transform the y-axis
|
||||
// to the opposite (so the image is then unchanged)
|
||||
worldMatrix.translate(0, image.height());
|
||||
worldMatrix.scale(1, -1);
|
||||
|
||||
m_painter->setWorldMatrix(worldMatrix);
|
||||
m_painter->drawImage(0, 0, image);
|
||||
|
||||
m_painter->restore();
|
||||
}
|
||||
|
||||
void PDFPainter::performUpdateGraphicsState(const PDFPageContentProcessorState& state)
|
||||
{
|
||||
const PDFPageContentProcessorState::StateFlags flags = state.getStateFlags();
|
||||
|
@@ -51,6 +51,7 @@ public:
|
||||
protected:
|
||||
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 performImagePainting(const QImage& image);
|
||||
virtual void performUpdateGraphicsState(const PDFPageContentProcessorState& state) override;
|
||||
virtual void performSaveGraphicState(ProcessOrder order) override;
|
||||
virtual void performRestoreGraphicState(ProcessOrder order) override;
|
||||
|
Reference in New Issue
Block a user