From 1f4fcfbd69d7ecfd0cde4734d1a31f5cddaee31c Mon Sep 17 00:00:00 2001 From: sbarman Date: Sun, 26 Jun 2011 22:54:18 -0700 Subject: [PATCH 1/4] refactored image drawing code --- pdf.js | 322 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 167 insertions(+), 155 deletions(-) diff --git a/pdf.js b/pdf.js index 2f21c95f0..8b8e6ff54 100644 --- a/pdf.js +++ b/pdf.js @@ -4234,23 +4234,11 @@ var CanvasGraphics = (function() { paintImageXObject: function(ref, image, inline) { this.save(); - if (image.getParams) { - // JPX/JPEG2000 streams directly contain bits per component - // and color space mode information. - TODO("get params from actual stream"); - // var bits = ... - // var colorspace = ... - } - // TODO cache rendered images? + var ctx = this.ctx; var dict = image.dict; var w = dict.get2("Width", "W"); var h = dict.get2("Height", "H"); - - if (w < 1 || h < 1) - error("Invalid image width or height"); - - var ctx = this.ctx; // scale the image to the unit square ctx.scale(1/w, -1/h); @@ -4265,154 +4253,15 @@ var CanvasGraphics = (function() { return; } - var interpolate = dict.get2("Interpolate", "I"); - if (!IsBool(interpolate)) - interpolate = false; - var imageMask = dict.get2("ImageMask", "IM"); - if (!IsBool(imageMask)) - imageMask = false; - - var bitsPerComponent = image.bitsPerComponent; - if (!bitsPerComponent) { - bitsPerComponent = dict.get2("BitsPerComponent", "BPC"); - if (!bitsPerComponent) { - if (imageMask) - bitsPerComponent = 1; - else - error("Bits per component missing in image"); - } - } - - if (bitsPerComponent !== 8) { - TODO("Support bpc="+ bitsPerComponent); - this.restore(); - return; - } - - var xref = this.xref; - var colorSpaces = this.colorSpaces; - - if (imageMask) { - error("support image masks"); - } - - // actual image - var csStream = dict.get2("ColorSpace", "CS"); - csStream = xref.fetchIfRef(csStream); - if (IsName(csStream) && inline) - csStream = colorSpaces.get(csStream); - - var colorSpace = new ColorSpace(xref, csStream); - var decode = dict.get2("Decode", "D"); - - TODO("create color map"); - - var mask = image.dict.get("Mask"); - mask = xref.fetchIfRef(mask); - var smask = image.dict.get("SMask"); - smask = xref.fetchIfRef(smask); - - if (IsStream(smask)) { - if (inline) - error("cannot combine smask and inlining"); - - var maskDict = smask.dict; - var maskW = maskDict.get2("Width", "W"); - var maskH = maskDict.get2("Height", "H"); - if (!IsNum(maskW) || !IsNum(maskH) || maskW < 1 || maskH < 1) - error("Invalid image width or height"); - if (maskW !== w || maskH !== h) - error("Invalid image width or height"); - - var maskInterpolate = maskDict.get2("Interpolate", "I"); - if (!IsBool(maskInterpolate)) - maskInterpolate = false; - - var maskBPC = maskDict.get2("BitsPerComponent", "BPC"); - if (!maskBPC) - error("Invalid image mask bpc"); - - var maskCsStream = maskDict.get2("ColorSpace", "CS"); - maskCsStream = xref.fetchIfRef(maskCsStream); - var maskColorSpace = new ColorSpace(xref, maskCsStream); - if (maskColorSpace.mode !== "DeviceGray") - error("Invalid color space for smask"); - - var maskDecode = maskDict.get2("Decode", "D"); - if (maskDecode) - TODO("Handle mask decode"); - // handle matte object - } + var imageObj = new PDFImage(this.xref, this.res, image, inline); var tmpCanvas = new this.ScratchCanvas(w, h); var tmpCtx = tmpCanvas.getContext("2d"); var imgData = tmpCtx.getImageData(0, 0, w, h); var pixels = imgData.data; - if (bitsPerComponent != 8) - error("unhandled number of bits per component"); - - if (smask) { - if (maskColorSpace.numComps != 1) - error("Incorrect number of components in smask"); - - var numComps = colorSpace.numComps; - var imgArray = image.getBytes(numComps * w * h); - var imgIdx = 0; - - var smArray = smask.getBytes(w * h); - var smIdx = 0; - - var length = 4 * w * h; - switch (numComps) { - case 1: - for (var i = 0; i < length; i += 4) { - var p = imgArray[imgIdx++]; - pixels[i] = p; - pixels[i+1] = p; - pixels[i+2] = p; - pixels[i+3] = smArray[smIdx++]; - } - break; - case 3: - for (var i = 0; i < length; i += 4) { - pixels[i] = imgArray[imgIdx++]; - pixels[i+1] = imgArray[imgIdx++]; - pixels[i+2] = imgArray[imgIdx++]; - pixels[i+3] = smArray[smIdx++]; - } - break; - default: - TODO("Images with "+ numComps + " components per pixel"); - } - } else { - var numComps = colorSpace.numComps; - var imgArray = image.getBytes(numComps * w * h); - var imgIdx = 0; - - var length = 4 * w * h; - switch (numComps) { - case 1: - for (var i = 0; i < length; i += 4) { - var p = imgArray[imgIdx++]; - pixels[i] = p; - pixels[i+1] = p; - pixels[i+2] = p; - pixels[i+3] = 255; - } - break; - case 3: - for (var i = 0; i < length; i += 4) { - pixels[i] = imgArray[imgIdx++]; - pixels[i+1] = imgArray[imgIdx++]; - pixels[i+2] = imgArray[imgIdx++]; - pixels[i+3] = 255; - } - break; - default: - TODO("Images with "+ numComps + " components per pixel"); - } - } + imageObj.fillRgbaBuffer(pixels); + tmpCtx.putImageData(imgData, 0, 0); ctx.drawImage(tmpCanvas, 0, -h); this.restore(); @@ -4541,6 +4390,169 @@ var ColorSpace = (function() { return constructor; })(); +var PDFImage = (function() { + function constructor(xref, res, image, inline) { + this.image = image; + if (image.getParams) { + // JPX/JPEG2000 streams directly contain bits per component + // and color space mode information. + TODO("get params from actual stream"); + // var bits = ... + // var colorspace = ... + } + // TODO cache rendered images? + + var dict = image.dict; + this.width = dict.get2("Width", "W"); + this.height = dict.get2("Height", "H"); + + if (this.width < 1 || this.height < 1) + error("Invalid image width or height"); + + this.interpolate = dict.get2("Interpolate", "I") || false; + this.imageMask = dict.get2("ImageMask", "IM") || false; + + var bitsPerComponent = image.bitsPerComponent; + if (!bitsPerComponent) { + bitsPerComponent = dict.get2("BitsPerComponent", "BPC"); + if (!bitsPerComponent) { + if (this.imageMask) + bitsPerComponent = 1; + else + error("Bits per component missing in image"); + } + } + this.bpc = bitsPerComponent; + + var colorSpaces = res.get("ColorSpace"); + var csStream = xref.fetchIfRef(dict.get2("ColorSpace", "CS")); + if (IsName(csStream) && inline) + csStream = colorSpaces.get(csStream); + this.colorSpace = new ColorSpace(xref, csStream); + + this.numComps = this.colorSpace.numComps; + this.decode = dict.get2("Decode", "D"); + + var mask = xref.fetchIfRef(image.dict.get("Mask")); + var smask = xref.fetchIfRef(image.dict.get("SMask")); + + if (mask) { + TODO("masked images"); + } else if (smask) { + this.smask = new PDFImage(xref, res, smask); + } + }; + + constructor.prototype = { + getCompFunction: function getCompFunction(bpc, width, numComps, buffer) { + var bufferPos = 0; + if (bpc == 8) { + var getComp = function() { + return buffer[bufferPos++]; + } + } else { + var rowBytes = (width * numComps * bpc + 7) >> 3; + var bits = 0; + var buf = 0; + var getComp = function() { + while (bits < bpc) { + buf = (buf << 8) | buffer[bufferPos++]; + bits += 8; + } + var remainingBits = bits - bpc; + var ret = buf >> remainingBits; + + if (bufferPos % rowBytes == 0) { + buf = 0; + bits = 0; + } else { + buf = buf & ((1 << remainingBits) - 1); + bits = remainingBits; + } + return Math.round(255 * ret / ((1 << bpc) - 1)); + } + } + return getComp; + }, + getOpacityFunction: function getOpacityFunction() { + var smask = this.smask; + if (smask) { + var w = smask.width; + var h = smask.height; + if (w != this.width || h != this.height) + error("smask dimensions do not match image dimensions"); + + var buf = new Uint8Array(w * h); + smask.fillGrayBuffer(buf); + var bufPos = 0; + var opacity = function() { + return buf[bufPos++]; + } + } else { + var opacity = function() { return 255; } + } + return opacity; + }, + fillRgbaBuffer: function fillRgbaBuffer(buffer) { + var numComps = this.numComps; + var width = this.width; + var height = this.height; + var bpc = this.bpc; + + // rows start at byte boundary; + var rowBytes = (width * numComps * bpc + 7) >> 3; + var imgArray = this.image.getBytes(height * rowBytes); + var imgPos = 0; + + var getComp = this.getCompFunction(bpc, width, numComps, imgArray) + var getOpacity = this.getOpacityFunction(); + var length = width * height * 4; + + switch (numComps) { + case 1: + for (var i = 0; i < length; i += 4) { + var p = getComp(); + buffer[i] = p; + buffer[i+1] = p; + buffer[i+2] = p; + buffer[i+3] = getOpacity(); + } + break; + case 3: + for (var i = 0; i < length; i += 4) { + buffer[i] = getComp(); + buffer[i+1] = getComp(); + buffer[i+2] = getComp(); + buffer[i+3] = getOpacity(); + } + break; + default: + TODO("Images with "+ numComps + " components per pixel"); + } + }, + fillGrayBuffer: function fillGrayScaleBuffer(buffer) { + var numComps = this.numComps; + if (numComps != 1) + error("Reading gray scale from a color image"); + + var width = this.width; + var height = this.height; + var bpc = this.bpc; + + // rows start at byte boundary; + var rowBytes = (width * numComps * bpc + 7) >> 3; + var imgArray = this.image.getBytes(height * rowBytes); + + var getComp = this.getCompFunction(bpc, width, numComps, imgArray) + var length = width * height; + + for (var i = 0; i < length; ++i) + buffer[i] = getComp(); + }, + }; + return constructor; +})(); + var PDFFunction = (function() { function constructor(xref, fn) { var dict = fn.dict; From 2fdb20062659b06baad531b4708ad0afc759f92e Mon Sep 17 00:00:00 2001 From: sbarman Date: Mon, 27 Jun 2011 09:58:02 -0700 Subject: [PATCH 2/4] working version of PDFImage --- pdf.js | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/pdf.js b/pdf.js index 8b8e6ff54..542c0d48a 100644 --- a/pdf.js +++ b/pdf.js @@ -4446,14 +4446,39 @@ var PDFImage = (function() { constructor.prototype = { getCompFunction: function getCompFunction(bpc, width, numComps, buffer) { var bufferPos = 0; - if (bpc == 8) { + if (bpc == 1) { + var rowComps = width * numComps; + var curComp = 0; + var mask = 0; + var buf = 0; + + var getComp = function() { + if (mask <= 0) { + buf = buffer[bufferPos++]; + mask = 128; + } + + var ret = buf & mask; + curComp++; + + if (curComp % rowComps == 0) { + mask = 0; + buf = 0; + } else { + mask >>= 1; + } + return ret * 255; + } + } else if (bpc == 8) { var getComp = function() { return buffer[bufferPos++]; } } else { - var rowBytes = (width * numComps * bpc + 7) >> 3; + var rowComps = width * numComps; + var curComp = 0; var bits = 0; var buf = 0; + var getComp = function() { while (bits < bpc) { buf = (buf << 8) | buffer[bufferPos++]; @@ -4461,8 +4486,9 @@ var PDFImage = (function() { } var remainingBits = bits - bpc; var ret = buf >> remainingBits; + curComp++; - if (bufferPos % rowBytes == 0) { + if (curComp % rowComps == 0) { buf = 0; bits = 0; } else { From c4d50b7677f78a628611c1532cc7162935da44aa Mon Sep 17 00:00:00 2001 From: sbarman Date: Mon, 27 Jun 2011 10:38:10 -0700 Subject: [PATCH 3/4] reasonably fast for black and white images --- pdf.js | 97 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/pdf.js b/pdf.js index 542c0d48a..0c6f12b50 100644 --- a/pdf.js +++ b/pdf.js @@ -4444,80 +4444,87 @@ var PDFImage = (function() { }; constructor.prototype = { - getCompFunction: function getCompFunction(bpc, width, numComps, buffer) { + getComponents: function getComponents(buffer) { + var bpc = this.bpc; + if (bpc == 8) + return buffer; + + var width = this.width; + var height = this.height; + var numComps = this.numComps; + + var length = width * height; var bufferPos = 0; + var output = new Uint8Array(length); + if (bpc == 1) { var rowComps = width * numComps; - var curComp = 0; var mask = 0; var buf = 0; - var getComp = function() { - if (mask <= 0) { - buf = buffer[bufferPos++]; - mask = 128; - } - - var ret = buf & mask; - curComp++; - - if (curComp % rowComps == 0) { + for (var i = 0, ii = length; i < ii; ++i) { + if (i % rowComps == 0) { mask = 0; buf = 0; } else { mask >>= 1; } - return ret * 255; - } - } else if (bpc == 8) { - var getComp = function() { - return buffer[bufferPos++]; + + if (mask <= 0) { + buf = buffer[bufferPos++]; + mask = 128; + } + + var t = buf & mask; + if (t == 0) + output[i] = 0; + else + output[i] = 255; } } else { var rowComps = width * numComps; - var curComp = 0; var bits = 0; var buf = 0; - var getComp = function() { + for (var i = 0, ii = length; i < ii; ++i) { while (bits < bpc) { buf = (buf << 8) | buffer[bufferPos++]; bits += 8; } var remainingBits = bits - bpc; var ret = buf >> remainingBits; - curComp++; - if (curComp % rowComps == 0) { + if (i % rowComps == 0) { buf = 0; bits = 0; } else { buf = buf & ((1 << remainingBits) - 1); bits = remainingBits; } - return Math.round(255 * ret / ((1 << bpc) - 1)); + output[i] = Math.round(255 * ret / ((1 << bpc) - 1)); } } - return getComp; + return output; }, - getOpacityFunction: function getOpacityFunction() { + getOpacity: function getOpacity() { var smask = this.smask; + var width = this.width; + var height = this.height; + var buf = new Uint8Array(width * height); + if (smask) { - var w = smask.width; - var h = smask.height; - if (w != this.width || h != this.height) + var sw = smask.width; + var sh = smask.height; + if (sw != this.width || sh != this.height) error("smask dimensions do not match image dimensions"); - var buf = new Uint8Array(w * h); smask.fillGrayBuffer(buf); - var bufPos = 0; - var opacity = function() { - return buf[bufPos++]; - } + return buf; } else { - var opacity = function() { return 255; } + for (var i = 0, ii = width * height; i < ii; ++i) + buf[i] = 255; } - return opacity; + return buf; }, fillRgbaBuffer: function fillRgbaBuffer(buffer) { var numComps = this.numComps; @@ -4530,26 +4537,28 @@ var PDFImage = (function() { var imgArray = this.image.getBytes(height * rowBytes); var imgPos = 0; - var getComp = this.getCompFunction(bpc, width, numComps, imgArray) - var getOpacity = this.getOpacityFunction(); + var comps = this.getComponents(imgArray); + var compsPos = 0; + var opacity = this.getOpacity(); + var opacityPos = 0; var length = width * height * 4; switch (numComps) { case 1: for (var i = 0; i < length; i += 4) { - var p = getComp(); + var p = comps[compsPos++]; buffer[i] = p; buffer[i+1] = p; buffer[i+2] = p; - buffer[i+3] = getOpacity(); + buffer[i+3] = opacity[opacityPos++]; } break; case 3: for (var i = 0; i < length; i += 4) { - buffer[i] = getComp(); - buffer[i+1] = getComp(); - buffer[i+2] = getComp(); - buffer[i+3] = getOpacity(); + buffer[i] = comps[compsPos++]; + buffer[i+1] = comps[compsPos++]; + buffer[i+2] = comps[compsPos++]; + buffer[i+3] = opacity[opacityPos++]; } break; default: @@ -4569,11 +4578,11 @@ var PDFImage = (function() { var rowBytes = (width * numComps * bpc + 7) >> 3; var imgArray = this.image.getBytes(height * rowBytes); - var getComp = this.getCompFunction(bpc, width, numComps, imgArray) + var comps = this.getComponents(imgArray); var length = width * height; for (var i = 0; i < length; ++i) - buffer[i] = getComp(); + buffer[i] = comps[i]; }, }; return constructor; From 51f3961fa2e89d51e57a626ff088582c7064f9c7 Mon Sep 17 00:00:00 2001 From: sbarman Date: Mon, 27 Jun 2011 10:51:13 -0700 Subject: [PATCH 4/4] clean up --- pdf.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index 0c6f12b50..43514ebf0 100644 --- a/pdf.js +++ b/pdf.js @@ -4535,7 +4535,6 @@ var PDFImage = (function() { // rows start at byte boundary; var rowBytes = (width * numComps * bpc + 7) >> 3; var imgArray = this.image.getBytes(height * rowBytes); - var imgPos = 0; var comps = this.getComponents(imgArray); var compsPos = 0; @@ -4565,7 +4564,7 @@ var PDFImage = (function() { TODO("Images with "+ numComps + " components per pixel"); } }, - fillGrayBuffer: function fillGrayScaleBuffer(buffer) { + fillGrayBuffer: function fillGrayBuffer(buffer) { var numComps = this.numComps; if (numComps != 1) error("Reading gray scale from a color image");