mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
JBIG2 - bitmap decoding
This commit is contained in:
@ -55,7 +55,7 @@ struct PDFCCITTFaxDecoderParameters
|
||||
/// to achieve byte alignment.
|
||||
bool hasEncodedByteAlign = false;
|
||||
|
||||
/// Flag indicating, that filter excepts the data be terminated by end of block bit pattern.
|
||||
/// Flag indicating, that filter expects the data be terminated by end of block bit pattern.
|
||||
/// In this case, \p rows parameter is ignored. Otherwise, rows parameter is used, or image
|
||||
/// is terminated by end of data stream, whichever occurs first. The end of block is marked
|
||||
/// as end-of-facsimile block (EOFB), or return to control (RTC), according the K parameter.
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "pdfjbig2decoder.h"
|
||||
#include "pdfexception.h"
|
||||
#include "pdfccittfaxdecoder.h"
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
@ -606,12 +607,12 @@ PDFImageData PDFJBIG2Decoder::decode(PDFImageData::MaskingType maskingType)
|
||||
{
|
||||
PDFBitWriter writer(1);
|
||||
|
||||
const size_t columns = m_pageBitmap.getWidth();
|
||||
const size_t rows = m_pageBitmap.getHeight();
|
||||
const int columns = m_pageBitmap.getWidth();
|
||||
const int rows = m_pageBitmap.getHeight();
|
||||
|
||||
for (size_t row = 0; row < rows; ++row)
|
||||
for (int row = 0; row < rows; ++row)
|
||||
{
|
||||
for (size_t column = 0; column < columns; ++column)
|
||||
for (int column = 0; column < columns; ++column)
|
||||
{
|
||||
writer.write(m_pageBitmap.getPixel(column, row));
|
||||
}
|
||||
@ -734,7 +735,87 @@ void PDFJBIG2Decoder::processHalftoneRegion(const PDFJBIG2SegmentHeader& header)
|
||||
|
||||
void PDFJBIG2Decoder::processGenericRegion(const PDFJBIG2SegmentHeader& header)
|
||||
{
|
||||
// TODO: JBIG2 - processGenericRegion
|
||||
const int segmentStartPosition = m_reader.getPosition();
|
||||
PDFJBIG2RegionSegmentInformationField field = readRegionSegmentInformationField();
|
||||
const uint8_t flags = m_reader.readUnsignedByte();
|
||||
|
||||
PDFJBIG2BitmapDecodingParameters parameters;
|
||||
parameters.MMR = flags & 0b0001;
|
||||
parameters.TPGDON = flags & 0b1000;
|
||||
parameters.GBTEMPLATE = (flags >> 1) & 0b0011;
|
||||
|
||||
if ((flags & 0b11110000) != 0)
|
||||
{
|
||||
throw PDFException(PDFTranslationContext::tr("JBIG2 - malformed generic region flags."));
|
||||
}
|
||||
|
||||
if (!parameters.MMR)
|
||||
{
|
||||
// We will use arithmetic coding, read template pixels and reset arithmetic coder state
|
||||
parameters.ATXY = readATTemplatePixelPositions((parameters.GBTEMPLATE == 0) ? 4 : 1);
|
||||
resetArithmeticStatesGeneric(parameters.GBTEMPLATE);
|
||||
}
|
||||
|
||||
// Determine segment data length
|
||||
const int segmentDataStartPosition = m_reader.getPosition();
|
||||
const int segmentHeaderBytes = segmentDataStartPosition - segmentStartPosition;
|
||||
int segmentDataBytes = 0;
|
||||
if (header.isSegmentDataLengthDefined())
|
||||
{
|
||||
segmentDataBytes = header.getSegmentDataLength() - segmentHeaderBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We must find byte sequence { 0x00, 0x00 } for MMR and { 0xFF, 0xAC } for arithmetic decoder
|
||||
const QByteArray* stream = m_reader.getStream();
|
||||
|
||||
QByteArray endSequence(2, 0);
|
||||
if (!parameters.MMR)
|
||||
{
|
||||
endSequence[0] = char(0xFF);
|
||||
endSequence[1] = char(0xAC);
|
||||
}
|
||||
|
||||
int endPosition = stream->indexOf(endSequence);
|
||||
if (endPosition == -1)
|
||||
{
|
||||
throw PDFException(PDFTranslationContext::tr("JBIG2 - end of data byte sequence not found for generic region."));
|
||||
}
|
||||
|
||||
// Add end bytes (they are also a part of stream)
|
||||
endPosition += endSequence.size();
|
||||
|
||||
segmentDataBytes = endPosition - segmentDataStartPosition;
|
||||
}
|
||||
parameters.data = m_reader.getStream()->mid(segmentDataStartPosition, segmentDataBytes);
|
||||
parameters.width = field.width;
|
||||
parameters.height = field.height;
|
||||
parameters.arithmeticDecoderState = &m_arithmeticDecoderStates[Generic];
|
||||
|
||||
PDFJBIG2Bitmap bitmap = readBitmap(parameters);
|
||||
if (bitmap.isValid())
|
||||
{
|
||||
if (header.isImmediate())
|
||||
{
|
||||
m_pageBitmap.paint(bitmap, field.offsetX, field.offsetY, field.operation, m_pageSizeUndefined, m_pageDefaultPixelValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_segments[header.getSegmentNumber()] = std::make_unique<PDFJBIG2Bitmap>(qMove(bitmap));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PDFException(PDFTranslationContext::tr("JBIG2 - invalid bitmap for generic region."));
|
||||
}
|
||||
|
||||
// Now skip the data
|
||||
m_reader.skipBytes(segmentDataBytes);
|
||||
|
||||
if (header.isImmediate() && !header.isSegmentDataLengthDefined())
|
||||
{
|
||||
m_reader.skipBytes(4);
|
||||
}
|
||||
}
|
||||
|
||||
void PDFJBIG2Decoder::processGenericRefinementRegion(const PDFJBIG2SegmentHeader& header)
|
||||
@ -782,6 +863,7 @@ void PDFJBIG2Decoder::processPageInformation(const PDFJBIG2SegmentHeader&)
|
||||
|
||||
const uint32_t correctedWidth = width;
|
||||
const uint32_t correctedHeight = (height != 0xFFFFFFFF) ? height : 0;
|
||||
m_pageSizeUndefined = height == 0xFFFFFFFF;
|
||||
|
||||
checkBitmapSize(correctedWidth);
|
||||
checkBitmapSize(correctedHeight);
|
||||
@ -901,6 +983,289 @@ void PDFJBIG2Decoder::processExtension(const PDFJBIG2SegmentHeader& header)
|
||||
}
|
||||
}
|
||||
|
||||
PDFJBIG2Bitmap PDFJBIG2Decoder::readBitmap(const PDFJBIG2BitmapDecodingParameters& parameters)
|
||||
{
|
||||
if (parameters.MMR)
|
||||
{
|
||||
// Use modified-modified-read (it corresponds to CCITT 2D encoding)
|
||||
PDFCCITTFaxDecoderParameters ccittParameters;
|
||||
ccittParameters.K = -1;
|
||||
ccittParameters.columns = parameters.width;
|
||||
ccittParameters.rows = parameters.height;
|
||||
ccittParameters.hasEndOfBlock = false;
|
||||
|
||||
PDFCCITTFaxDecoder decoder(¶meters.data, ccittParameters);
|
||||
PDFImageData data = decoder.decode();
|
||||
|
||||
PDFJBIG2Bitmap bitmap(data.getWidth(), data.getHeight(), m_pageDefaultPixelValue);
|
||||
|
||||
// Copy the data
|
||||
PDFBitReader reader(&data.getData(), data.getBitsPerComponent());
|
||||
for (unsigned int row = 0; row < data.getHeight(); ++row)
|
||||
{
|
||||
for (unsigned int column = 0; column < data.getWidth(); ++column)
|
||||
{
|
||||
bitmap.setPixel(column, row, (reader.read()) ? 0xFF : 0x00);
|
||||
}
|
||||
|
||||
reader.alignToBytes();
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use arithmetic encoding. For templates, we fill bytes from right to left, from bottom to top bits,
|
||||
// filling from lowest bit to highest bit. We will have a maximum of 16 bits.
|
||||
|
||||
uint8_t LTP = 0;
|
||||
uint16_t LTPContext = 0;
|
||||
if (parameters.TPGDON)
|
||||
{
|
||||
switch (parameters.GBTEMPLATE)
|
||||
{
|
||||
case 0:
|
||||
LTPContext = 0b1010010011011001; // 16-bit context, hexadecimal value is 0x9B25
|
||||
break;
|
||||
|
||||
case 1:
|
||||
LTPContext = 0b0011110010101; // 13-bit context, hexadecimal value is 0x0795
|
||||
break;
|
||||
|
||||
case 2:
|
||||
LTPContext = 0b0011100101; // 10-bit context, hexadecimal value is 0x00E5
|
||||
break;
|
||||
|
||||
case 3:
|
||||
LTPContext = 0b0110010101; // 10-bit context, hexadecimal value is 0x0195
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PDFBitReader reader(¶meters.data, 1);
|
||||
PDFJBIG2ArithmeticDecoder decoder(&reader);
|
||||
decoder.initialize();
|
||||
|
||||
PDFJBIG2Bitmap bitmap(parameters.width, parameters.height, 0x00);
|
||||
for (int y = 0; y < parameters.height; ++y)
|
||||
{
|
||||
// Check TPGDON prediction - if we use same pixels as in previous line
|
||||
if (parameters.TPGDON)
|
||||
{
|
||||
LTP = LTP ^ decoder.readBit(LTPContext, parameters.arithmeticDecoderState);
|
||||
if (LTP)
|
||||
{
|
||||
if (y > 0)
|
||||
{
|
||||
bitmap.copyRow(y, y - 1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = 0; x < parameters.width; ++x)
|
||||
{
|
||||
// Check, if we have to skip pixel. Pixel should be set to 0, but it is done
|
||||
// in the initialization of the bitmap.
|
||||
if (parameters.SKIP && parameters.SKIP->getPixelSafe(x, y))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t pixelContext = 0;
|
||||
uint16_t pixelContextShift = 0;
|
||||
auto createContextBit = [&](int offsetX, int offsetY)
|
||||
{
|
||||
uint16_t bit = bitmap.getPixelSafe(offsetX, offsetY) ? 1 : 0;
|
||||
bit = bit << pixelContextShift;
|
||||
pixelContext |= bit;
|
||||
++pixelContextShift;
|
||||
};
|
||||
|
||||
// Create pixel context based on used template
|
||||
switch (parameters.GBTEMPLATE)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// 16-bit context
|
||||
createContextBit(x - 1, y);
|
||||
createContextBit(x - 2, y);
|
||||
createContextBit(x - 3, y);
|
||||
createContextBit(x - 4, y);
|
||||
createContextBit(x + parameters.ATXY[0].x, y + parameters.ATXY[0].y);
|
||||
createContextBit(x + 2, y - 1);
|
||||
createContextBit(x + 1, y - 1);
|
||||
createContextBit(x + 0, y - 1);
|
||||
createContextBit(x - 1, y - 1);
|
||||
createContextBit(x - 2, y - 1);
|
||||
createContextBit(x + parameters.ATXY[1].x, y + parameters.ATXY[1].y);
|
||||
createContextBit(x + parameters.ATXY[2].x, y + parameters.ATXY[2].y);
|
||||
createContextBit(x + 1, y - 2);
|
||||
createContextBit(x + 0, y - 2);
|
||||
createContextBit(x - 1, y - 2);
|
||||
createContextBit(x + parameters.ATXY[3].x, y + parameters.ATXY[3].y);
|
||||
break;
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
// 13-bit context
|
||||
createContextBit(x - 1, y);
|
||||
createContextBit(x - 2, y);
|
||||
createContextBit(x - 3, y);
|
||||
createContextBit(x + parameters.ATXY[0].x, y + parameters.ATXY[0].y);
|
||||
createContextBit(x + 2, y - 1);
|
||||
createContextBit(x + 1, y - 1);
|
||||
createContextBit(x + 0, y - 1);
|
||||
createContextBit(x - 1, y - 1);
|
||||
createContextBit(x - 2, y - 1);
|
||||
createContextBit(x + 2, y - 2);
|
||||
createContextBit(x + 1, y - 2);
|
||||
createContextBit(x + 0, y - 2);
|
||||
createContextBit(x - 1, y - 2);
|
||||
break;
|
||||
}
|
||||
|
||||
case 2:
|
||||
{
|
||||
// 10-bit context
|
||||
createContextBit(x - 1, y);
|
||||
createContextBit(x - 2, y);
|
||||
createContextBit(x + parameters.ATXY[0].x, y + parameters.ATXY[0].y);
|
||||
createContextBit(x + 1, y - 1);
|
||||
createContextBit(x + 0, y - 1);
|
||||
createContextBit(x - 1, y - 1);
|
||||
createContextBit(x - 2, y - 1);
|
||||
createContextBit(x + 1, y - 2);
|
||||
createContextBit(x + 0, y - 2);
|
||||
createContextBit(x - 1, y - 2);
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
{
|
||||
// 10-bit context
|
||||
createContextBit(x - 1, y);
|
||||
createContextBit(x - 2, y);
|
||||
createContextBit(x - 3, y);
|
||||
createContextBit(x - 4, y);
|
||||
createContextBit(x + parameters.ATXY[0].x, y + parameters.ATXY[0].y);
|
||||
createContextBit(x + 1, y - 1);
|
||||
createContextBit(x + 0, y - 1);
|
||||
createContextBit(x - 1, y - 1);
|
||||
createContextBit(x - 2, y - 1);
|
||||
createContextBit(x - 3, y - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.setPixel(x, y, (decoder.readBit(pixelContext, parameters.arithmeticDecoderState)) ? 0xFF : 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
return PDFJBIG2Bitmap();
|
||||
}
|
||||
|
||||
PDFJBIG2RegionSegmentInformationField PDFJBIG2Decoder::readRegionSegmentInformationField()
|
||||
{
|
||||
PDFJBIG2RegionSegmentInformationField result;
|
||||
|
||||
result.width = m_reader.readUnsignedInt();
|
||||
result.height = m_reader.readUnsignedInt();
|
||||
result.offsetX = m_reader.readUnsignedInt();
|
||||
result.offsetY = m_reader.readUnsignedInt();
|
||||
|
||||
// Parse flags
|
||||
const uint8_t flags = m_reader.readUnsignedByte();
|
||||
|
||||
if ((flags & 0b11111000) != 0)
|
||||
{
|
||||
// This is forbidden by the specification
|
||||
throw PDFException(PDFTranslationContext::tr("JBIG2 region segment information flags are invalid."));
|
||||
}
|
||||
|
||||
switch (flags)
|
||||
{
|
||||
case 0:
|
||||
result.operation = PDFJBIG2BitOperation::Or;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
result.operation = PDFJBIG2BitOperation::And;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
result.operation = PDFJBIG2BitOperation::Xor;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
result.operation = PDFJBIG2BitOperation::NotXor;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
result.operation = PDFJBIG2BitOperation::Replace;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw PDFException(PDFTranslationContext::tr("JBIG2 region segment information - invalid bit operation mode."));
|
||||
}
|
||||
|
||||
checkRegionSegmentInformationField(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
PDFJBIG2ATPositions PDFJBIG2Decoder::readATTemplatePixelPositions(int count)
|
||||
{
|
||||
PDFJBIG2ATPositions result = { };
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
result[i].x = m_reader.readSignedByte();
|
||||
result[i].y = m_reader.readSignedByte();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PDFJBIG2Decoder::resetArithmeticStatesGeneric(const uint8_t templateMode)
|
||||
{
|
||||
uint8_t bits = 0;
|
||||
switch (templateMode)
|
||||
{
|
||||
case 0:
|
||||
bits = 16;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
bits = 13;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
bits = 10;
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
m_arithmeticDecoderStates[Generic].reset(bits);
|
||||
}
|
||||
|
||||
void PDFJBIG2Decoder::skipSegment(const PDFJBIG2SegmentHeader& header)
|
||||
{
|
||||
if (header.isSegmentDataLengthDefined())
|
||||
@ -921,6 +1286,24 @@ void PDFJBIG2Decoder::checkBitmapSize(const uint32_t size)
|
||||
}
|
||||
}
|
||||
|
||||
void PDFJBIG2Decoder::checkRegionSegmentInformationField(const PDFJBIG2RegionSegmentInformationField& field)
|
||||
{
|
||||
checkBitmapSize(field.width);
|
||||
checkBitmapSize(field.height);
|
||||
checkBitmapSize(field.offsetX);
|
||||
checkBitmapSize(field.offsetY);
|
||||
|
||||
if (field.width == 0 || field.height == 0)
|
||||
{
|
||||
throw PDFException(PDFTranslationContext::tr("JBIG2 invalid bitmap size (%1 x %2).").arg(field.width).arg(field.height));
|
||||
}
|
||||
|
||||
if (field.operation == PDFJBIG2BitOperation::Invalid)
|
||||
{
|
||||
throw PDFException(PDFTranslationContext::tr("JBIG2 invalid bit operation."));
|
||||
}
|
||||
}
|
||||
|
||||
PDFJBIG2Bitmap::PDFJBIG2Bitmap() :
|
||||
m_width(0),
|
||||
m_height(0)
|
||||
@ -928,20 +1311,94 @@ PDFJBIG2Bitmap::PDFJBIG2Bitmap() :
|
||||
|
||||
}
|
||||
|
||||
PDFJBIG2Bitmap::PDFJBIG2Bitmap(size_t width, size_t height) :
|
||||
PDFJBIG2Bitmap::PDFJBIG2Bitmap(int width, int height) :
|
||||
m_width(width),
|
||||
m_height(height)
|
||||
{
|
||||
m_data.resize(width * height, 0);
|
||||
}
|
||||
|
||||
PDFJBIG2Bitmap::PDFJBIG2Bitmap(size_t width, size_t height, uint8_t fill) :
|
||||
PDFJBIG2Bitmap::PDFJBIG2Bitmap(int width, int height, uint8_t fill) :
|
||||
m_width(width),
|
||||
m_height(height)
|
||||
{
|
||||
m_data.resize(width * height, fill);
|
||||
}
|
||||
|
||||
void PDFJBIG2Bitmap::paint(const PDFJBIG2Bitmap& bitmap, int offsetX, int offsetY, PDFJBIG2BitOperation operation, bool expandY, const uint8_t expandPixel)
|
||||
{
|
||||
if (!bitmap.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Expand, if it is allowed and target bitmap has too low height
|
||||
if (expandY && offsetY + bitmap.getHeight() > m_height)
|
||||
{
|
||||
m_height = offsetY + bitmap.getHeight();
|
||||
m_data.resize(getPixelCount(), expandPixel);
|
||||
}
|
||||
|
||||
// Check out pathological cases
|
||||
if (offsetX >= m_width || offsetY >= m_height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int targetStartX = offsetX;
|
||||
const int targetEndX = qMin(offsetX + bitmap.getWidth(), m_width);
|
||||
const int targetStartY = offsetY;
|
||||
const int targetEndY = qMin(offsetY + bitmap.getHeight(), m_height);
|
||||
|
||||
for (int targetY = targetStartY; targetY < targetEndY; ++targetY)
|
||||
{
|
||||
for (int targetX = targetStartX; targetX < targetEndX; ++targetX)
|
||||
{
|
||||
const int sourceX = targetX - targetStartX;
|
||||
const int sourceY = targetY - targetStartY;
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case PDFJBIG2BitOperation::Or:
|
||||
setPixel(targetX, targetY, getPixel(targetX, targetY) | bitmap.getPixel(sourceX, sourceY));
|
||||
break;
|
||||
|
||||
case PDFJBIG2BitOperation::And:
|
||||
setPixel(targetX, targetY, getPixel(targetX, targetY) & bitmap.getPixel(sourceX, sourceY));
|
||||
break;
|
||||
|
||||
case PDFJBIG2BitOperation::Xor:
|
||||
setPixel(targetX, targetY, getPixel(targetX, targetY) ^ bitmap.getPixel(sourceX, sourceY));
|
||||
break;
|
||||
|
||||
case PDFJBIG2BitOperation::NotXor:
|
||||
setPixel(targetX, targetY, getPixel(targetX, targetY) ^ (~bitmap.getPixel(sourceX, sourceY)));
|
||||
break;
|
||||
|
||||
case PDFJBIG2BitOperation::Replace:
|
||||
setPixel(targetX, targetY, bitmap.getPixel(sourceX, sourceY));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw PDFException(PDFTranslationContext::tr("JBIG2 - invalid bitmap paint operation."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PDFJBIG2Bitmap::copyRow(int target, int source)
|
||||
{
|
||||
if (target < 0 || target >= m_height || source < 0 || source >= m_height)
|
||||
{
|
||||
throw PDFException(PDFTranslationContext::tr("JBIG2 - invalid bitmap copy row operation."));
|
||||
}
|
||||
|
||||
auto itSource = std::next(m_data.cbegin(), source * m_width);
|
||||
auto itSourceEnd = std::next(itSource, m_width);
|
||||
auto itTarget = std::next(m_data.begin(), target * m_width);
|
||||
std::copy(itSource, itSourceEnd, itTarget);
|
||||
}
|
||||
|
||||
PDFJBIG2HuffmanCodeTable::PDFJBIG2HuffmanCodeTable(std::vector<PDFJBIG2HuffmanTableEntry>&& entries) :
|
||||
m_entries(qMove(entries))
|
||||
{
|
||||
|
@ -35,7 +35,8 @@ enum class PDFJBIG2BitOperation
|
||||
Or,
|
||||
And,
|
||||
Xor,
|
||||
NotXor
|
||||
NotXor,
|
||||
Replace
|
||||
};
|
||||
|
||||
/// Arithmetic decoder state for JBIG2 data streams. It contains state for context,
|
||||
@ -45,12 +46,24 @@ enum class PDFJBIG2BitOperation
|
||||
class PDFJBIG2ArithmeticDecoderState
|
||||
{
|
||||
public:
|
||||
explicit inline PDFJBIG2ArithmeticDecoderState() = default;
|
||||
explicit inline PDFJBIG2ArithmeticDecoderState(size_t size) :
|
||||
m_state(size, 0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// Resets the context
|
||||
inline void reset(const uint8_t bits)
|
||||
{
|
||||
size_t size = (1ULL << bits);
|
||||
std::fill(m_state.begin(), m_state.end(), 0);
|
||||
if (m_state.size() != size)
|
||||
{
|
||||
m_state.resize(size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns row index to Qe value table, according to document ISO/IEC 14492:2001,
|
||||
/// annex E, table E.1 (Qe values and probability estimation process).
|
||||
inline uint8_t getQeRowIndex(size_t context) const
|
||||
@ -86,16 +99,17 @@ private:
|
||||
class PDFJBIG2ArithmeticDecoder
|
||||
{
|
||||
public:
|
||||
explicit inline PDFJBIG2ArithmeticDecoder() :
|
||||
explicit inline PDFJBIG2ArithmeticDecoder(PDFBitReader* reader) :
|
||||
m_c(0),
|
||||
m_a(0),
|
||||
m_ct(0),
|
||||
m_reader(nullptr)
|
||||
m_reader(reader)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void initialize() { perform_INITDEC(); }
|
||||
uint32_t readBit(size_t context, PDFJBIG2ArithmeticDecoderState* state) { return perform_DECODE(context, state); }
|
||||
|
||||
private:
|
||||
/// Performs INITDEC operation as described in the specification
|
||||
@ -220,17 +234,27 @@ class PDFJBIG2Bitmap : public PDFJBIG2Segment
|
||||
{
|
||||
public:
|
||||
explicit PDFJBIG2Bitmap();
|
||||
explicit PDFJBIG2Bitmap(size_t width, size_t height);
|
||||
explicit PDFJBIG2Bitmap(size_t width, size_t height, uint8_t fill);
|
||||
explicit PDFJBIG2Bitmap(int width, int height);
|
||||
explicit PDFJBIG2Bitmap(int width, int height, uint8_t fill);
|
||||
|
||||
virtual const PDFJBIG2Bitmap* asBitmap() const override { return this; }
|
||||
virtual PDFJBIG2Bitmap* asBitmap() override { return this; }
|
||||
|
||||
inline size_t getWidth() const { return m_width; }
|
||||
inline size_t getHeight() const { return m_height; }
|
||||
inline size_t getPixelCount() const { return m_width * m_height; }
|
||||
inline uint8_t getPixel(size_t x, size_t y) const { return m_data[y * m_width + x]; }
|
||||
inline void setPixel(size_t x, size_t y, uint8_t value) { m_data[y * m_width + x] = value; }
|
||||
inline int getWidth() const { return m_width; }
|
||||
inline int getHeight() const { return m_height; }
|
||||
inline int getPixelCount() const { return m_width * m_height; }
|
||||
inline uint8_t getPixel(int x, int y) const { return m_data[y * m_width + x]; }
|
||||
inline void setPixel(int x, int y, uint8_t value) { m_data[y * m_width + x] = value; }
|
||||
|
||||
inline uint8_t getPixelSafe(int x, int y) const
|
||||
{
|
||||
if (x < 0 || x >= m_width || y < 0 || y >= m_height)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getPixel(x, y);
|
||||
}
|
||||
|
||||
inline void fill(uint8_t value) { std::fill(m_data.begin(), m_data.end(), value); }
|
||||
inline void fillZero() { fill(0); }
|
||||
@ -238,12 +262,79 @@ public:
|
||||
|
||||
inline bool isValid() const { return getPixelCount() > 0; }
|
||||
|
||||
/// Paints another bitmap onto this bitmap. If bitmap is invalid, nothing is done.
|
||||
/// If \p expandY is true, height of target bitmap is expanded to fit source draw area.
|
||||
/// \param bitmap Bitmap to be painted on this
|
||||
/// \param offsetX Horizontal offset of paint area
|
||||
/// \param offsetY Vertical offset of paint area
|
||||
/// \param operation Paint operation to be performed
|
||||
/// \param expandY Expand vertically, if painted bitmap exceeds current bitmap area
|
||||
/// \param expandPixel Initialize pixels by this value during expanding
|
||||
void paint(const PDFJBIG2Bitmap& bitmap, int offsetX, int offsetY, PDFJBIG2BitOperation operation, bool expandY, const uint8_t expandPixel);
|
||||
|
||||
/// Copies data from source row to target row. If source or target row doesn't exists,
|
||||
/// then exception is thrown.
|
||||
/// \param target Target row
|
||||
/// \param source Source row
|
||||
void copyRow(int target, int source);
|
||||
|
||||
private:
|
||||
size_t m_width;
|
||||
size_t m_height;
|
||||
int m_width;
|
||||
int m_height;
|
||||
std::vector<uint8_t> m_data;
|
||||
};
|
||||
|
||||
/// Region segment information field, see chapter 7.4.1 in the specification
|
||||
struct PDFJBIG2RegionSegmentInformationField
|
||||
{
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t offsetX = 0;
|
||||
uint32_t offsetY = 0;
|
||||
PDFJBIG2BitOperation operation = PDFJBIG2BitOperation::Invalid;
|
||||
};
|
||||
|
||||
/// Info structure for adaptative template
|
||||
struct PDFJBIG2ATPosition
|
||||
{
|
||||
int8_t x = 0;
|
||||
int8_t y = 0;
|
||||
};
|
||||
|
||||
using PDFJBIG2ATPositions = std::array<PDFJBIG2ATPosition, 4>;
|
||||
|
||||
/// Info structure for bitmap decoding parameters
|
||||
struct PDFJBIG2BitmapDecodingParameters
|
||||
{
|
||||
/// Is Modified-Modified-Read encoding used? This encoding is simalr to CCITT pure 2D encoding.
|
||||
bool MMR = false;
|
||||
|
||||
/// Is typical prediction for generic direct coding used?
|
||||
bool TPGDON = false;
|
||||
|
||||
/// Width of the image
|
||||
int width = 0;
|
||||
|
||||
/// Height of the image
|
||||
int height = 0;
|
||||
|
||||
/// Template mode (not used for MMR).
|
||||
uint8_t GBTEMPLATE = 0;
|
||||
|
||||
/// Positions of adaptative pixels
|
||||
PDFJBIG2ATPositions ATXY = { };
|
||||
|
||||
/// Data with encoded image
|
||||
QByteArray data;
|
||||
|
||||
/// State of arithmetic decoder
|
||||
PDFJBIG2ArithmeticDecoderState* arithmeticDecoderState = nullptr;
|
||||
|
||||
/// Skip bitmap (pixel is skipped if corresponding pixel in the
|
||||
/// skip bitmap is 1). Set to nullptr, if not used.
|
||||
const PDFJBIG2Bitmap* SKIP = nullptr;
|
||||
};
|
||||
|
||||
/// Decoder of JBIG2 data streams. Decodes the black/white monochrome image.
|
||||
/// Handles also global segments. Decoder decodes data using the specification
|
||||
/// ISO/IEC 14492:2001, T.88.
|
||||
@ -257,7 +348,9 @@ public:
|
||||
m_reader(nullptr, 8),
|
||||
m_pageDefaultPixelValue(0),
|
||||
m_pageDefaultCompositionOperator(PDFJBIG2BitOperation::Invalid),
|
||||
m_pageDefaultCompositionOperatorOverriden(false)
|
||||
m_pageDefaultCompositionOperatorOverriden(false),
|
||||
m_pageSizeUndefined(false),
|
||||
m_arithmeticDecoderStates()
|
||||
{
|
||||
|
||||
}
|
||||
@ -270,6 +363,12 @@ public:
|
||||
private:
|
||||
static constexpr const uint32_t MAX_BITMAP_SIZE = 65536;
|
||||
|
||||
enum ArithmeticDecoderStates
|
||||
{
|
||||
Generic,
|
||||
EndState
|
||||
};
|
||||
|
||||
/// Processes current data stream (reads all data from the stream, interprets
|
||||
/// them as segments and processes the segments).
|
||||
void processStream();
|
||||
@ -288,21 +387,35 @@ private:
|
||||
void processCodeTables(const PDFJBIG2SegmentHeader& header);
|
||||
void processExtension(const PDFJBIG2SegmentHeader& header);
|
||||
|
||||
/// Reads the bitmap using decoding parameters
|
||||
/// \param parameters Decoding parameters
|
||||
PDFJBIG2Bitmap readBitmap(const PDFJBIG2BitmapDecodingParameters& parameters);
|
||||
|
||||
/// Reads the region segment information field (see chapter 7.4.1)
|
||||
PDFJBIG2RegionSegmentInformationField readRegionSegmentInformationField();
|
||||
|
||||
/// Read adaptative pixel template positions, positions, which are not read, are filled with 0
|
||||
PDFJBIG2ATPositions readATTemplatePixelPositions(int count);
|
||||
|
||||
/// Reset arithmetic decoder stats for generic
|
||||
void resetArithmeticStatesGeneric(const uint8_t templateMode);
|
||||
|
||||
void skipSegment(const PDFJBIG2SegmentHeader& header);
|
||||
|
||||
static void checkBitmapSize(const uint32_t size);
|
||||
static void checkRegionSegmentInformationField(const PDFJBIG2RegionSegmentInformationField& field);
|
||||
|
||||
QByteArray m_data;
|
||||
QByteArray m_globalData;
|
||||
PDFRenderErrorReporter* m_errorReporter;
|
||||
PDFBitReader m_reader;
|
||||
std::map<uint32_t, std::unique_ptr<PDFJBIG2Segment>> m_segments;
|
||||
|
||||
/// Page default pixel value
|
||||
uint8_t m_pageDefaultPixelValue;
|
||||
PDFJBIG2BitOperation m_pageDefaultCompositionOperator;
|
||||
bool m_pageDefaultCompositionOperatorOverriden;
|
||||
bool m_pageSizeUndefined;
|
||||
PDFJBIG2Bitmap m_pageBitmap;
|
||||
std::array<PDFJBIG2ArithmeticDecoderState, EndState> m_arithmeticDecoderStates;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
@ -126,6 +126,12 @@ int32_t PDFBitReader::readSignedInt()
|
||||
return *reinterpret_cast<const int32_t*>(&value);
|
||||
}
|
||||
|
||||
int8_t PDFBitReader::readSignedByte()
|
||||
{
|
||||
const uint8_t value = read(8);
|
||||
return *reinterpret_cast<const int8_t*>(&value);
|
||||
}
|
||||
|
||||
PDFBitWriter::PDFBitWriter(Value bitsPerComponent) :
|
||||
m_bitsPerComponent(bitsPerComponent),
|
||||
m_mask((static_cast<Value>(1) << m_bitsPerComponent) - static_cast<Value>(1)),
|
||||
|
@ -138,6 +138,9 @@ public:
|
||||
/// Reads signed 32-bit integer from the stream
|
||||
int32_t readSignedInt();
|
||||
|
||||
/// Reads signed 8-bit integer from the stream
|
||||
int8_t readSignedByte();
|
||||
|
||||
/// Reads unsigned 32-bit integer from the stream
|
||||
uint32_t readUnsignedInt() { return read(32); }
|
||||
|
||||
@ -147,6 +150,9 @@ public:
|
||||
/// Reads unsigned 8-bit integer from the stream
|
||||
uint8_t readUnsignedByte() { return read(8); }
|
||||
|
||||
/// Return underlying byte stream
|
||||
const QByteArray* getStream() const { return m_stream; }
|
||||
|
||||
private:
|
||||
const QByteArray* m_stream;
|
||||
int m_position;
|
||||
|
Reference in New Issue
Block a user