Merge pull request #4336 from nnethercote/rgb24

Special-case 24-bit RGB image-handling
This commit is contained in:
Yury Delendik 2014-03-02 19:53:42 -06:00
commit 9a918572dd
4 changed files with 82 additions and 51 deletions

View File

@ -16,7 +16,7 @@
*/ */
/* globals assert, assertWellFormed, ColorSpace, Dict, Encodings, error, /* globals assert, assertWellFormed, ColorSpace, Dict, Encodings, error,
ErrorFont, Font, FONT_IDENTITY_MATRIX, fontCharsToUnicode, FontFlags, ErrorFont, Font, FONT_IDENTITY_MATRIX, fontCharsToUnicode, FontFlags,
info, isArray, isCmd, isDict, isEOF, isName, isNum, ImageKind, info, isArray, isCmd, isDict, isEOF, isName, isNum,
isStream, isString, JpegStream, Lexer, Metrics, Name, Parser, isStream, isString, JpegStream, Lexer, Metrics, Name, Parser,
Pattern, PDFImage, PDFJS, serifFonts, stdFontMap, symbolsFonts, Pattern, PDFImage, PDFJS, serifFonts, stdFontMap, symbolsFonts,
getTilingPatternIR, warn, Util, Promise, LegacyPromise, getTilingPatternIR, warn, Util, Promise, LegacyPromise,
@ -164,7 +164,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
(w + h) < SMALL_IMAGE_DIMENSIONS) { (w + h) < SMALL_IMAGE_DIMENSIONS) {
var imageObj = new PDFImage(this.xref, resources, image, var imageObj = new PDFImage(this.xref, resources, image,
inline, null, null); inline, null, null);
var imgData = imageObj.createImageData(); // We force the use of RGBA_32BPP images here, because we can't handle
// any other kind.
var imgData = imageObj.createImageData(/* forceRGBA = */ true);
operatorList.addOp(OPS.paintInlineImageXObject, [imgData]); operatorList.addOp(OPS.paintInlineImageXObject, [imgData]);
return; return;
} }
@ -187,7 +189,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
PDFImage.buildImage(function(imageObj) { PDFImage.buildImage(function(imageObj) {
var imgData = imageObj.createImageData(); var imgData = imageObj.createImageData(/* forceRGBA = */ false);
self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData], self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData],
null, [imgData.data.buffer]); null, [imgData.data.buffer]);
}, self.handler, self.xref, resources, image, inline); }, self.handler, self.xref, resources, image, inline);
@ -1313,7 +1315,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// replacing queue items // replacing queue items
squash(fnArray, j, count * 4, OPS.paintInlineImageXObjectGroup); squash(fnArray, j, count * 4, OPS.paintInlineImageXObjectGroup);
argsArray.splice(j, count * 4, argsArray.splice(j, count * 4,
[{width: imgWidth, height: imgHeight, kind: 'rgba_32bpp', [{width: imgWidth, height: imgHeight, kind: ImageKind.RGBA_32BPP,
data: imgData}, map]); data: imgData}, map]);
i = j; i = j;
ii = argsArray.length; ii = argsArray.length;

View File

@ -14,8 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* globals ColorSpace, error, isArray, isStream, JpegStream, Name, Promise, /* globals ColorSpace, error, isArray, ImageKind, isStream, JpegStream, Name,
Stream, warn, LegacyPromise */ Promise, Stream, warn, LegacyPromise */
'use strict'; 'use strict';
@ -417,7 +417,7 @@ var PDFImage = (function PDFImageClosure() {
buffer[i + 2] = clamp((buffer[i + 2] - matteRgb[2]) * k + matteRgb[2]); buffer[i + 2] = clamp((buffer[i + 2] - matteRgb[2]) * k + matteRgb[2]);
} }
}, },
createImageData: function PDFImage_createImageData() { createImageData: function PDFImage_createImageData(forceRGBA) {
var drawWidth = this.drawWidth; var drawWidth = this.drawWidth;
var drawHeight = this.drawHeight; var drawHeight = this.drawHeight;
var imgData = { // other fields are filled in below var imgData = { // other fields are filled in below
@ -430,32 +430,41 @@ var PDFImage = (function PDFImageClosure() {
var originalHeight = this.height; var originalHeight = this.height;
var bpc = this.bpc; var bpc = this.bpc;
// rows start at byte boundary; // Rows start at byte boundary.
var rowBytes = (originalWidth * numComps * bpc + 7) >> 3; var rowBytes = (originalWidth * numComps * bpc + 7) >> 3;
var imgArray = this.getImageBytes(originalHeight * rowBytes); var imgArray = this.getImageBytes(originalHeight * rowBytes);
// imgArray can be incomplete (e.g. after CCITT fax encoding) if (!forceRGBA) {
// If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image
// without any complications, we pass a same-sized copy to the main
// thread rather than expanding by 32x to RGBA form. This saves *lots*
// of memory for many scanned documents. It's also much faster.
//
// Similarly, if it is a 24-bit-per pixel RGB image without any
// complications, we avoid expanding by 1.333x to RGBA form.
var kind;
if (this.colorSpace.name === 'DeviceGray' && bpc === 1) {
kind = ImageKind.GRAYSCALE_1BPP;
} else if (this.colorSpace.name === 'DeviceRGB' && bpc === 8) {
kind = ImageKind.RGB_24BPP;
}
if (kind && !this.smask && !this.mask && !this.needsDecode &&
drawWidth === originalWidth && drawHeight === originalHeight) {
imgData.kind = kind;
// We must make a copy of imgArray, otherwise it'll be neutered upon
// transfer which will break any code that subsequently reuses it.
var newArray = new Uint8Array(imgArray.length);
newArray.set(imgArray);
imgData.data = newArray;
return imgData;
}
}
// imgArray can be incomplete (e.g. after CCITT fax encoding).
var actualHeight = 0 | (imgArray.length / rowBytes * var actualHeight = 0 | (imgArray.length / rowBytes *
drawHeight / originalHeight); drawHeight / originalHeight);
// If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image
// without any complications, we pass a same-sized copy to the main
// thread rather than expanding by 32x to RGBA form. This saves *lots* of
// memory for many scanned documents. It's also much faster.
if (this.colorSpace.name === 'DeviceGray' && bpc === 1 &&
!this.smask && !this.mask && !this.needsDecode &&
drawWidth === originalWidth && drawHeight === originalHeight) {
imgData.kind = 'grayscale_1bpp';
// We must make a copy of imgArray, otherwise it'll be neutered upon
// transfer which will break any code that subsequently reuses it.
var newArray = new Uint8Array(imgArray.length);
newArray.set(imgArray);
imgData.data = newArray;
imgData.origLength = imgArray.length;
return imgData;
}
var comps = this.getComponents(imgArray); var comps = this.getComponents(imgArray);
var rgbaBuf = new Uint8Array(drawWidth * drawHeight * 4); var rgbaBuf = new Uint8Array(drawWidth * drawHeight * 4);
@ -473,7 +482,7 @@ var PDFImage = (function PDFImageClosure() {
this.undoPreblend(rgbaBuf, drawWidth, actualHeight); this.undoPreblend(rgbaBuf, drawWidth, actualHeight);
imgData.kind = 'rgba_32bpp'; imgData.kind = ImageKind.RGBA_32BPP;
imgData.data = rgbaBuf; imgData.data = rgbaBuf;
return imgData; return imgData;
}, },

View File

@ -15,9 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
/* globals ColorSpace, DeviceCmykCS, DeviceGrayCS, DeviceRgbCS, error, /* globals ColorSpace, DeviceCmykCS, DeviceGrayCS, DeviceRgbCS, error,
FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageData, isArray, isNum, FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageData, ImageKind,
TilingPattern, OPS, Promise, Util, warn, assert, info, shadow, isArray, isNum, TilingPattern, OPS, Promise, Util, warn, assert,
TextRenderingMode, getShadingPatternFromIR */ info, shadow, TextRenderingMode, getShadingPatternFromIR */
'use strict'; 'use strict';
@ -452,19 +452,17 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var chunkImgData = ctx.createImageData(width, fullChunkHeight); var chunkImgData = ctx.createImageData(width, fullChunkHeight);
var srcPos = 0; var srcPos = 0;
var src = imgData.data; var src = imgData.data;
var dst = chunkImgData.data; var dest = chunkImgData.data;
// There are multiple forms in which the pixel data can be passed, and // There are multiple forms in which the pixel data can be passed, and
// imgData.kind tells us which one this is. // imgData.kind tells us which one this is.
if (imgData.kind === 'grayscale_1bpp') { if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
// Grayscale, 1 bit per pixel (i.e. black-and-white). // Grayscale, 1 bit per pixel (i.e. black-and-white).
var srcData = imgData.data; var destDataLength = dest.length;
var destData = chunkImgData.data; var srcLength = src.byteLength;
var destDataLength = destData.length;
var origLength = imgData.origLength;
for (var i = 3; i < destDataLength; i += 4) { for (var i = 3; i < destDataLength; i += 4) {
destData[i] = 255; dest[i] = 255;
} }
for (var i = 0; i < totalChunks; i++) { for (var i = 0; i < totalChunks; i++) {
var thisChunkHeight = var thisChunkHeight =
@ -475,21 +473,21 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var srcByte = 0; var srcByte = 0;
for (var k = 0; k < width; k++, destPos += 4) { for (var k = 0; k < width; k++, destPos += 4) {
if (mask === 0) { if (mask === 0) {
if (srcPos >= origLength) { if (srcPos >= srcLength) {
break; break;
} }
srcByte = srcData[srcPos++]; srcByte = src[srcPos++];
mask = 128; mask = 128;
} }
if ((srcByte & mask)) { if ((srcByte & mask)) {
destData[destPos] = 255; dest[destPos] = 255;
destData[destPos + 1] = 255; dest[destPos + 1] = 255;
destData[destPos + 2] = 255; dest[destPos + 2] = 255;
} else { } else {
destData[destPos] = 0; dest[destPos] = 0;
destData[destPos + 1] = 0; dest[destPos + 1] = 0;
destData[destPos + 2] = 0; dest[destPos + 2] = 0;
} }
mask >>= 1; mask >>= 1;
@ -499,7 +497,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// We ran out of input. Make all remaining pixels transparent. // We ran out of input. Make all remaining pixels transparent.
destPos += 3; destPos += 3;
do { do {
destData[destPos] = 0; dest[destPos] = 0;
destPos += 4; destPos += 4;
} while (destPos < destDataLength); } while (destPos < destDataLength);
} }
@ -507,25 +505,41 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
} }
} else if (imgData.kind === 'rgba_32bpp') { } else if (imgData.kind === ImageKind.RGBA_32BPP) {
// RGBA, 32-bits per pixel. // RGBA, 32-bits per pixel.
var haveSetAndSubarray = 'set' in dst && 'subarray' in src; var haveSetAndSubarray = 'set' in dest && 'subarray' in src;
for (var i = 0; i < totalChunks; i++) { for (var i = 0; i < totalChunks; i++) {
var thisChunkHeight = var thisChunkHeight =
(i < fullChunks) ? fullChunkHeight : partialChunkHeight; (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
var elemsInThisChunk = imgData.width * thisChunkHeight * 4; var elemsInThisChunk = imgData.width * thisChunkHeight * 4;
if (haveSetAndSubarray) { if (haveSetAndSubarray) {
dst.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
srcPos += elemsInThisChunk; srcPos += elemsInThisChunk;
} else { } else {
for (var j = 0; j < elemsInThisChunk; j++) { for (var j = 0; j < elemsInThisChunk; j++) {
chunkImgData.data[j] = imgData.data[srcPos++]; dest[j] = src[srcPos++];
} }
} }
ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
} }
} else if (imgData.kind === ImageKind.RGB_24BPP) {
// RGB, 24-bits per pixel.
for (var i = 0; i < totalChunks; i++) {
var thisChunkHeight =
(i < fullChunks) ? fullChunkHeight : partialChunkHeight;
var elemsInThisChunk = imgData.width * thisChunkHeight * 3;
var destPos = 0;
for (var j = 0; j < elemsInThisChunk; j += 3) {
dest[destPos++] = src[srcPos++];
dest[destPos++] = src[srcPos++];
dest[destPos++] = src[srcPos++];
dest[destPos++] = 255;
}
ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
}
} else { } else {
error('bad image kind: ' + imgData.kind); error('bad image kind: ' + imgData.kind);
} }

View File

@ -38,6 +38,12 @@ var TextRenderingMode = {
ADD_TO_PATH_FLAG: 4 ADD_TO_PATH_FLAG: 4
}; };
var ImageKind = {
GRAYSCALE_1BPP: 1,
RGB_24BPP: 2,
RGBA_32BPP: 3
};
// The global PDFJS object exposes the API // The global PDFJS object exposes the API
// In production, it will be declared outside a global wrapper // In production, it will be declared outside a global wrapper
// In development, it will be declared here // In development, it will be declared here