From 5da69f520d06fd24093518ca82d5fd866d4b9fa2 Mon Sep 17 00:00:00 2001 From: sbarman Date: Wed, 8 Jun 2011 17:45:50 -0700 Subject: [PATCH] Half working version of paintImageXObject --- pdf.js | 219 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 179 insertions(+), 40 deletions(-) diff --git a/pdf.js b/pdf.js index 58ca1b70b..fa631f878 100644 --- a/pdf.js +++ b/pdf.js @@ -1894,7 +1894,7 @@ var CanvasGraphics = (function() { var type = xobj.dict.get("Subtype"); assertWellFormed(IsName(type), "XObject should have a Name subtype"); if ("Image" == type.name) { - this.paintImageXObject(xobj); + this.paintImageXObject(xobj, false); } else if ("Form" == type.name) { this.paintFormXObject(xobj); } else if ("PS" == type.name) { @@ -1924,16 +1924,23 @@ var CanvasGraphics = (function() { this.restore(); }, - paintImageXObject: function(image) { + paintImageXObject: function(image, inline) { this.save(); - + this.ctx.scale(.0066,.02); + 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; var w = dict.get("Width") || dict.get("W"); var h = dict.get("Height") || dict.get("H"); - - if (w < 1 || h < 1) + + if (!IsNum(w) || !IsNum(h) || w < 1 || h < 1) error("Invalid image width or height"); var interpolate = dict.get("Interpolate") || dict.get("I"); @@ -1943,11 +1950,7 @@ var CanvasGraphics = (function() { if (!IsBool(imageMask)) imageMask = false; - // JPX/JPEG2000 streams directly contain bits per component - // and color space mode information. var bitsPerComponent = image.bitsPerComponent; - var csMode = image.csMode; - if (!bitsPerComponent) { bitsPerComponent = dict.get("BitsPerComponent") || dict.get("BPC"); if (!bitsPerComponent) { @@ -1958,53 +1961,142 @@ var CanvasGraphics = (function() { } } + if (bitsPerComponent !== 8) + error("Unsupported bpc"); + + var xref = this.xref; + var colorSpaces = this.colorSpaces; + + if (imageMask) { + error("support image masks"); + } + + // actual image + var csStream = dict.get("ColorSpace") || dict.get("CS"); + csStream = xref.fetchIfRef(csStream); + if (IsName(csStream) && inline) + csStream = colorSpaces.get(csStream); + + var colorSpace = new ColorSpace(xref, csStream); + + var decode = dict.get("Decode") || dict.get("D"); + + TODO("create color map"); + + var mask = image.dict.get("Mask"); + mask = this.xref.fetchIfRef(mask); + var smask = image.dict.get("SMask"); + smask = this.xref.fetchIfRef(smask); + + if (IsStream(smask)) { + if (inline) + error("cannot combine smask and inlining"); + + var maskDict = smask.dict; + var maskW = maskDict.get("Width") || maskDict.get("W"); + var maskH = maskDict.get("Height") || maskDict.get("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.get("Interpolate") || maskDict.get("I"); + if (!IsBool(maskInterpolate)) + maskInterpolate = false; + + var maskBPC = maskDict.get("BitsPerComponent") + || maskDict.get("BPC"); + if (!maskBPC) + error("Invalid image mask bpc"); + + var maskCsStream = maskDict.get("ColorSpace") + || maskDict.get("CS"); + maskCsStream = xref.fetchIfRef(maskCsStream); + //if (IsName(maskCsStream)) + // maskCsStream = colorSpaces.get(maskCsStream); + + var maskColorSpace = new ColorSpace(xref, maskCsStream); + if (maskColorSpace.mode !== "DeviceGray") + error("Invalid color space for smask"); + + var maskDecode = maskDict.get("Decode") || maskDict.get("D"); + if (maskDecode) + TODO("Handle mask decode"); + // handle matte object + } else { + smask = null; + } + var tmpCanvas = document.createElement("canvas"); tmpCanvas.width = w; tmpCanvas.height = h; +// tmpCanvas.mozOpaque = false; + var ctx = this.ctx; var tmpCtx = tmpCanvas.getContext("2d"); + tmpCtx.fillStyle = "rgb(255, 255, 255)"; + tmpCtx.fillRect(0, 0, w, h); var imgData = tmpCtx.getImageData(0, 0, w, h); var pixels = imgData.data; - - var alpha = 25; - if (image.dict.has("SMask")) { - var smask = image.dict.get("SMask"); - smask = this.xref.fetchIfRef(smask); - // Specifies either a shape or opacity mask to be - // applied to the image samples - TODO("SMask"); - } - + if (smask) { - var smaskDict = smask.dict; - if (!smaskDict) - error("No dictionary for smask"); - var smaskBitsPerComponent = smaskDict.get("BitsPerComponent") || smaskDict.get("BPC"); - if (!smaskBitsPerComponent) - error("Bad BPC for smask"); - var max = (1 << bitsPerComponent) - 1; + if (maskColorSpace.numComps != 1) + error("Incorrect number of components in smask"); + + var numComps = colorSpace.numComps; + // factor in bits per component + var imgArray = image.getBytes(numComps * w * h); + var imgIdx = 0; - var matte = smaskDict.get("Matte"); - if (matte) { - TODO(matte); - } + var smArray = smask.getBytes(w * h); + var smIdx = 0; + + // hoist out loop end + for (var i = 0; i < 4 * w * h; i+=4) { + var alpha = smArray[smIdx++]; + var alphaComp = (1 << maskBPC) - 1 - alpha; - for (var i = 0; i < 4 * w * h; ++i) { - pixels[i] = image.getChar() * smask.getChar() / max; + switch (numComps) { + case 1: + var p = imgArray[imageIdx++]*alpha; + pixels[i] = (p + pixels[i]*alphaComp) >> maskBPC; + pixels[i+1] = (p + pixels[i+1]*alphaComp) >> maskBPC; + pixels[i+2] = (p + pixels[i+2]*alphaComp) >> maskBPC; + pixels[i+3] = 255; + break; + case 3: + pixels[i] = imgArray[imgIdx++]; //*alpha) >> maskBPC; + pixels[i+1] = imgArray[imgIdx++];//*alpha) >> maskBPC; + pixels[i+2] = imgArray[imgIdx++];//*alpha) >> maskBPC; + pixels[i+3] = alpha; + break; + default: + error("unhandled amount of components per pixel: " + numComps); + } } } else { - for (var i = 0; i < 4 * w * h; ++i) { - // TODO blend if SMask is a mask image - if (3 === i % 4) { - pixels[i] = alpha; - } else { - pixels[i] = image.getChar(); + var numComps = colorSpace.numComps; + for (var i = 0; i < 4 * w * h; i+=4) { + switch (numComps) { + case 1: + var t = image.getByte(); + pixels[i] = t; + pixels[i+1] = t; + pixels[i+2] = t; + pixels[i+3] = 255; + break; + case 3: + pixels[i] = image.getByte(); + pixels[i+1] = image.getByte(); + pixels[i+2] = image.getByte(); + pixels[i+3] = 255; + break; + default: + error("unhandled amount of components per pixel: " + numComps); } } } tmpCtx.putImageData(imgData, 0, 0); - this.ctx.drawImage(tmpCanvas, 0, 0); - this.restore(); }, @@ -2044,6 +2136,53 @@ var CanvasGraphics = (function() { return constructor; })(); +var ColorSpace = (function() { + function constructor(xref, cs) { + if (IsName(cs)) { + var mode = cs.name; + this.mode = mode; + switch(mode) { + case "DeviceGray": + case "G": + this.numComps = 1; + break; + } + TODO("fill in color space constructor"); + } else if (IsArray(cs)) { + var mode = cs[0].name; + this.mode = mode; + + var stream = cs[1]; + stream = xref.fetchIfRef(stream); + + switch (mode) { + case "DeviceGray": + case "G": + this.stream = stream; + this.dict = stream.dict; + this.numComps = 1; + break; + case "ICCBased": + var dict = stream.dict; + + this.stream = stream; + this.dict = dict; + this.numComps = dict.get("N"); + break; + default: + error("unrecognized color space object"); + } + } else { + error("unrecognized color space object"); + } + }; + + constructor.prototype = { + }; + + return constructor; +})(); + function runParseTests() { var data = snarf("paper.pdf", "binary"); var pdf = new PDFDoc(new Stream(data));