Implement JBIG2 halftone regions and pattern dictionaries
This commit is contained in:
parent
a1d88d8e2e
commit
9a581ee9ed
@ -111,7 +111,7 @@ var Jbig2Image = (function Jbig2ImageClosure() {
|
|||||||
var SegmentTypes = [
|
var SegmentTypes = [
|
||||||
'SymbolDictionary', null, null, null, 'IntermediateTextRegion', null,
|
'SymbolDictionary', null, null, null, 'IntermediateTextRegion', null,
|
||||||
'ImmediateTextRegion', 'ImmediateLosslessTextRegion', null, null, null,
|
'ImmediateTextRegion', 'ImmediateLosslessTextRegion', null, null, null,
|
||||||
null, null, null, null, null, 'patternDictionary', null, null, null,
|
null, null, null, null, null, 'PatternDictionary', null, null, null,
|
||||||
'IntermediateHalftoneRegion', null, 'ImmediateHalftoneRegion',
|
'IntermediateHalftoneRegion', null, 'ImmediateHalftoneRegion',
|
||||||
'ImmediateLosslessHalftoneRegion', null, null, null, null, null, null, null,
|
'ImmediateLosslessHalftoneRegion', null, null, null, null, null, null, null,
|
||||||
null, null, null, null, null, 'IntermediateGenericRegion', null,
|
null, null, null, null, null, 'IntermediateGenericRegion', null,
|
||||||
@ -619,6 +619,148 @@ var Jbig2Image = (function Jbig2ImageClosure() {
|
|||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decodePatternDictionary(mmr, patternWidth, patternHeight,
|
||||||
|
maxPatternIndex, template, decodingContext) {
|
||||||
|
let at = [];
|
||||||
|
at.push({
|
||||||
|
x: -patternWidth,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
if (template === 0) {
|
||||||
|
at.push({
|
||||||
|
x: -3,
|
||||||
|
y: -1,
|
||||||
|
});
|
||||||
|
at.push({
|
||||||
|
x: 2,
|
||||||
|
y: -2,
|
||||||
|
});
|
||||||
|
at.push({
|
||||||
|
x: -2,
|
||||||
|
y: -2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let collectiveWidth = (maxPatternIndex + 1) * patternWidth;
|
||||||
|
let collectiveBitmap = decodeBitmap(mmr, collectiveWidth, patternHeight,
|
||||||
|
template, false, null, at,
|
||||||
|
decodingContext);
|
||||||
|
// Divide collective bitmap into patterns.
|
||||||
|
let patterns = [], i = 0, patternBitmap, xMin, xMax, y;
|
||||||
|
while (i <= maxPatternIndex) {
|
||||||
|
patternBitmap = [];
|
||||||
|
xMin = patternWidth * i;
|
||||||
|
xMax = xMin + patternWidth;
|
||||||
|
for (y = 0; y < patternHeight; y++) {
|
||||||
|
patternBitmap.push(collectiveBitmap[y].subarray(xMin, xMax));
|
||||||
|
}
|
||||||
|
patterns.push(patternBitmap);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeHalftoneRegion(mmr, patterns, template, regionWidth,
|
||||||
|
regionHeight, defaultPixelValue, enableSkip,
|
||||||
|
combinationOperator, gridWidth, gridHeight,
|
||||||
|
gridOffsetX, gridOffsetY, gridVectorX,
|
||||||
|
gridVectorY, decodingContext) {
|
||||||
|
let skip = null;
|
||||||
|
if (enableSkip) {
|
||||||
|
throw new Jbig2Error('skip is not supported');
|
||||||
|
}
|
||||||
|
if (combinationOperator !== 0) {
|
||||||
|
throw new Jbig2Error('operator ' + combinationOperator +
|
||||||
|
' is not supported in halftone region');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare bitmap.
|
||||||
|
let regionBitmap = [];
|
||||||
|
let i, j, row;
|
||||||
|
for (i = 0; i < regionHeight; i++) {
|
||||||
|
row = new Uint8Array(regionWidth);
|
||||||
|
if (defaultPixelValue) {
|
||||||
|
for (j = 0; j < regionWidth; j++) {
|
||||||
|
row[j] = defaultPixelValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
regionBitmap.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
let numberOfPatterns = patterns.length;
|
||||||
|
let pattern0 = patterns[0];
|
||||||
|
let patternWidth = pattern0[0].length, patternHeight = pattern0.length;
|
||||||
|
let bitsPerValue = log2(numberOfPatterns);
|
||||||
|
let at = [];
|
||||||
|
at.push({
|
||||||
|
x: (template <= 1) ? 3 : 2,
|
||||||
|
y: -1,
|
||||||
|
});
|
||||||
|
if (template === 0) {
|
||||||
|
at.push({
|
||||||
|
x: -3,
|
||||||
|
y: -1,
|
||||||
|
});
|
||||||
|
at.push({
|
||||||
|
x: 2,
|
||||||
|
y: -2,
|
||||||
|
});
|
||||||
|
at.push({
|
||||||
|
x: -2,
|
||||||
|
y: -2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Annex C. Gray-scale Image Decoding Procedure.
|
||||||
|
let grayScaleBitPlanes = [];
|
||||||
|
for (i = bitsPerValue - 1; i >= 0; i--) {
|
||||||
|
grayScaleBitPlanes[i] = decodeBitmap(mmr, gridWidth, gridHeight,
|
||||||
|
template, false, skip, at,
|
||||||
|
decodingContext);
|
||||||
|
}
|
||||||
|
// 6.6.5.2 Rendering the patterns.
|
||||||
|
let mg, ng, bit, patternIndex, patternBitmap, x, y, patternRow, regionRow;
|
||||||
|
for (mg = 0; mg < gridHeight; mg++) {
|
||||||
|
for (ng = 0; ng < gridWidth; ng++) {
|
||||||
|
bit = 0;
|
||||||
|
patternIndex = 0;
|
||||||
|
for (j = bitsPerValue - 1; j >= 0; j--) {
|
||||||
|
bit = grayScaleBitPlanes[j][mg][ng] ^ bit; // Gray decoding
|
||||||
|
patternIndex |= bit << j;
|
||||||
|
}
|
||||||
|
patternBitmap = patterns[patternIndex];
|
||||||
|
x = (gridOffsetX + mg * gridVectorY + ng * gridVectorX) >> 8;
|
||||||
|
y = (gridOffsetY + mg * gridVectorX - ng * gridVectorY) >> 8;
|
||||||
|
// Draw patternBitmap at (x, y).
|
||||||
|
if (x >= 0 && x + patternWidth <= regionWidth && y >= 0 &&
|
||||||
|
y + patternHeight <= regionHeight) {
|
||||||
|
for (i = 0; i < patternHeight; i++) {
|
||||||
|
regionRow = regionBitmap[y + i];
|
||||||
|
patternRow = patternBitmap[i];
|
||||||
|
for (j = 0; j < patternWidth; j++) {
|
||||||
|
regionRow[x + j] |= patternRow[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let regionX, regionY;
|
||||||
|
for (i = 0; i < patternHeight; i++) {
|
||||||
|
regionY = y + i;
|
||||||
|
if (regionY < 0 || regionY >= regionHeight) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
regionRow = regionBitmap[regionY];
|
||||||
|
patternRow = patternBitmap[i];
|
||||||
|
for (j = 0; j < patternWidth; j++) {
|
||||||
|
regionX = x + j;
|
||||||
|
if (regionX >= 0 && regionX < regionWidth) {
|
||||||
|
regionRow[regionX] |= patternRow[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return regionBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
function readSegmentHeader(data, start) {
|
function readSegmentHeader(data, start) {
|
||||||
var segmentHeader = {};
|
var segmentHeader = {};
|
||||||
segmentHeader.number = readUint32(data, start);
|
segmentHeader.number = readUint32(data, start);
|
||||||
@ -850,6 +992,44 @@ var Jbig2Image = (function Jbig2ImageClosure() {
|
|||||||
}
|
}
|
||||||
args = [textRegion, header.referredTo, data, position, end];
|
args = [textRegion, header.referredTo, data, position, end];
|
||||||
break;
|
break;
|
||||||
|
case 16: // PatternDictionary
|
||||||
|
// 7.4.4. Pattern dictionary segment syntax
|
||||||
|
let patternDictionary = {};
|
||||||
|
let patternDictionaryFlags = data[position++];
|
||||||
|
patternDictionary.mmr = !!(patternDictionaryFlags & 1);
|
||||||
|
patternDictionary.template = (patternDictionaryFlags >> 1) & 3;
|
||||||
|
patternDictionary.patternWidth = data[position++];
|
||||||
|
patternDictionary.patternHeight = data[position++];
|
||||||
|
patternDictionary.maxPatternIndex = readUint32(data, position);
|
||||||
|
position += 4;
|
||||||
|
args = [patternDictionary, header.number, data, position, end];
|
||||||
|
break;
|
||||||
|
case 22: // ImmediateHalftoneRegion
|
||||||
|
case 23: // ImmediateLosslessHalftoneRegion
|
||||||
|
// 7.4.5 Halftone region segment syntax
|
||||||
|
let halftoneRegion = {};
|
||||||
|
halftoneRegion.info = readRegionSegmentInformation(data, position);
|
||||||
|
position += RegionSegmentInformationFieldLength;
|
||||||
|
let halftoneRegionFlags = data[position++];
|
||||||
|
halftoneRegion.mmr = !!(halftoneRegionFlags & 1);
|
||||||
|
halftoneRegion.template = (halftoneRegionFlags >> 1) & 3;
|
||||||
|
halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8);
|
||||||
|
halftoneRegion.combinationOperator = (halftoneRegionFlags >> 4) & 7;
|
||||||
|
halftoneRegion.defaultPixelValue = (halftoneRegionFlags >> 7) & 1;
|
||||||
|
halftoneRegion.gridWidth = readUint32(data, position);
|
||||||
|
position += 4;
|
||||||
|
halftoneRegion.gridHeight = readUint32(data, position);
|
||||||
|
position += 4;
|
||||||
|
halftoneRegion.gridOffsetX = readUint32(data, position) & 0xFFFFFFFF;
|
||||||
|
position += 4;
|
||||||
|
halftoneRegion.gridOffsetY = readUint32(data, position) & 0xFFFFFFFF;
|
||||||
|
position += 4;
|
||||||
|
halftoneRegion.gridVectorX = readUint16(data, position);
|
||||||
|
position += 2;
|
||||||
|
halftoneRegion.gridVectorY = readUint16(data, position);
|
||||||
|
position += 2;
|
||||||
|
args = [halftoneRegion, header.referredTo, data, position, end];
|
||||||
|
break;
|
||||||
case 38: // ImmediateGenericRegion
|
case 38: // ImmediateGenericRegion
|
||||||
case 39: // ImmediateLosslessGenericRegion
|
case 39: // ImmediateLosslessGenericRegion
|
||||||
var genericRegion = {};
|
var genericRegion = {};
|
||||||
@ -1086,6 +1266,32 @@ var Jbig2Image = (function Jbig2ImageClosure() {
|
|||||||
function SimpleSegmentVisitor_onImmediateLosslessTextRegion() {
|
function SimpleSegmentVisitor_onImmediateLosslessTextRegion() {
|
||||||
this.onImmediateTextRegion.apply(this, arguments);
|
this.onImmediateTextRegion.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
onPatternDictionary(dictionary, currentSegment, data, start, end) {
|
||||||
|
let patterns = this.patterns;
|
||||||
|
if (!patterns) {
|
||||||
|
this.patterns = patterns = {};
|
||||||
|
}
|
||||||
|
let decodingContext = new DecodingContext(data, start, end);
|
||||||
|
patterns[currentSegment] = decodePatternDictionary(dictionary.mmr,
|
||||||
|
dictionary.patternWidth, dictionary.patternHeight,
|
||||||
|
dictionary.maxPatternIndex, dictionary.template, decodingContext);
|
||||||
|
},
|
||||||
|
onImmediateHalftoneRegion(region, referredSegments, data, start, end) {
|
||||||
|
// HalftoneRegion refers to exactly one PatternDictionary.
|
||||||
|
let patterns = this.patterns[referredSegments[0]];
|
||||||
|
let regionInfo = region.info;
|
||||||
|
let decodingContext = new DecodingContext(data, start, end);
|
||||||
|
let bitmap = decodeHalftoneRegion(region.mmr, patterns,
|
||||||
|
region.template, regionInfo.width, regionInfo.height,
|
||||||
|
region.defaultPixelValue, region.enableSkip, region.combinationOperator,
|
||||||
|
region.gridWidth, region.gridHeight, region.gridOffsetX,
|
||||||
|
region.gridOffsetY, region.gridVectorX, region.gridVectorY,
|
||||||
|
decodingContext);
|
||||||
|
this.drawBitmap(regionInfo, bitmap);
|
||||||
|
},
|
||||||
|
onImmediateLosslessHalftoneRegion() {
|
||||||
|
this.onImmediateHalftoneRegion.apply(this, arguments);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function Jbig2Image() {}
|
function Jbig2Image() {}
|
||||||
|
1
test/pdfs/pr8491.pdf.link
Normal file
1
test/pdfs/pr8491.pdf.link
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://github.com/mozilla/pdf.js/files/1055278/castle_halftone.pdf
|
@ -3618,5 +3618,12 @@
|
|||||||
"md5": "ced0e2d88cfd5b4d3a55d937ea288af1",
|
"md5": "ced0e2d88cfd5b4d3a55d937ea288af1",
|
||||||
"rounds": 1,
|
"rounds": 1,
|
||||||
"type": "eq"
|
"type": "eq"
|
||||||
|
},
|
||||||
|
{ "id": "pr8491",
|
||||||
|
"file": "pdfs/pr8491.pdf",
|
||||||
|
"md5": "36ea2e28cd77e9e70731f574ab27cbe0",
|
||||||
|
"rounds": 1,
|
||||||
|
"link": true,
|
||||||
|
"type": "eq"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user