mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-02-28 17:37:46 +01:00
Minor fixes of images
This commit is contained in:
parent
5896196f56
commit
630afbba61
@ -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();
|
||||||
|
@ -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>;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user