From 1f4fcfbd69d7ecfd0cde4734d1a31f5cddaee31c Mon Sep 17 00:00:00 2001 From: sbarman Date: Sun, 26 Jun 2011 22:54:18 -0700 Subject: [PATCH] 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;