diff --git a/src/core/colorspace.js b/src/core/colorspace.js index 0f1dc36e2..0189368e3 100644 --- a/src/core/colorspace.js +++ b/src/core/colorspace.js @@ -380,23 +380,20 @@ var AlternateCS = (function AlternateCSClosure() { } this.base = base; this.tintFn = tintFn; + this.tmpBuf = new Float32Array(base.numComps); } AlternateCS.prototype = { getRgb: ColorSpace.prototype.getRgb, getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, dest, destOffset) { - var baseNumComps = this.base.numComps; - var input = 'subarray' in src ? - src.subarray(srcOffset, srcOffset + this.numComps) : - Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); - var tinted = this.tintFn(input); - this.base.getRgbItem(tinted, 0, dest, destOffset); + var tmpBuf = this.tmpBuf; + this.tintFn(src, srcOffset, tmpBuf, 0); + this.base.getRgbItem(tmpBuf, 0, dest, destOffset); }, getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { - var tinted; var tintFn = this.tintFn; var base = this.base; var scale = 1 / ((1 << bits) - 1); @@ -409,13 +406,14 @@ var AlternateCS = (function AlternateCSClosure() { var numComps = this.numComps; var scaled = new Float32Array(numComps); + var tinted = new Float32Array(baseNumComps); var i, j; if (usesZeroToOneRange) { for (i = 0; i < count; i++) { for (j = 0; j < numComps; j++) { scaled[j] = src[srcOffset++] * scale; } - tinted = tintFn(scaled); + tintFn(scaled, 0, tinted, 0); for (j = 0; j < baseNumComps; j++) { baseBuf[pos++] = tinted[j] * 255; } @@ -425,7 +423,7 @@ var AlternateCS = (function AlternateCSClosure() { for (j = 0; j < numComps; j++) { scaled[j] = src[srcOffset++] * scale; } - tinted = tintFn(scaled); + tintFn(scaled, 0, tinted, 0); base.getRgbItem(tinted, 0, baseBuf, pos); pos += baseNumComps; } diff --git a/src/core/function.js b/src/core/function.js index 073dba312..296757dd8 100644 --- a/src/core/function.js +++ b/src/core/function.js @@ -97,6 +97,24 @@ var PDFFunction = (function PDFFunctionClosure() { return this.fromIR(IR); }, + parseArray: function PDFFunction_parseArray(xref, fnObj) { + if (!isArray(fnObj)) { + // not an array -- parsing as regular function + return this.parse(xref, fnObj); + } + + var fnArray = []; + for (var j = 0, jj = fnObj.length; j < jj; j++) { + var obj = xref.fetchIfRef(fnObj[j]); + fnArray.push(PDFFunction.parse(xref, obj)); + } + return function (src, srcOffset, dest, destOffset) { + for (var i = 0, ii = fnArray.length; i < ii; i++) { + fnArray[i](src, srcOffset, dest, destOffset + i); + } + }; + }, + constructSampled: function PDFFunction_constructSampled(str, dict) { function toMultiArray(arr) { var inputLength = arr.length; @@ -161,7 +179,8 @@ var PDFFunction = (function PDFFunctionClosure() { return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); } - return function constructSampledFromIRResult(args) { + return function constructSampledFromIRResult(src, srcOffset, + dest, destOffset) { // See chapter 3, page 110 of the PDF reference. var m = IR[1]; var domain = IR[2]; @@ -173,13 +192,6 @@ var PDFFunction = (function PDFFunctionClosure() { //var mask = IR[8]; var range = IR[9]; - if (m !== args.length) { - error('Incorrect number of arguments: ' + m + ' != ' + - args.length); - } - - var x = args; - // Building the cube vertices: its part and sample index // http://rjwagner49.com/Mathematics/Interpolation.pdf var cubeVertices = 1 << m; @@ -196,7 +208,8 @@ var PDFFunction = (function PDFFunctionClosure() { // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) var domain_2i = domain[i][0]; var domain_2i_1 = domain[i][1]; - var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); + var xi = Math.min(Math.max(src[srcOffset +i], domain_2i), + domain_2i_1); // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, // Encode_2i, Encode_2i+1) @@ -227,7 +240,6 @@ var PDFFunction = (function PDFFunctionClosure() { pos <<= 1; } - var y = new Float64Array(n); for (j = 0; j < n; ++j) { // Sum all cube vertices' samples portions var rj = 0; @@ -240,10 +252,9 @@ var PDFFunction = (function PDFFunctionClosure() { rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); // y_j = min(max(r_j, range_2j), range_2j+1) - y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); + dest[destOffset + j] = Math.min(Math.max(rj, range[j][0]), + range[j][1]); } - - return y; }; }, @@ -274,16 +285,13 @@ var PDFFunction = (function PDFFunctionClosure() { var length = diff.length; - return function constructInterpolatedFromIRResult(args) { - var x = (n === 1 ? args[0] : Math.pow(args[0], n)); + return function constructInterpolatedFromIRResult(src, srcOffset, + dest, destOffset) { + var x = n === 1 ? src[srcOffset] : Math.pow(src[srcOffset], n); - var out = []; for (var j = 0; j < length; ++j) { - out.push(c0[j] + (x * diff[j])); + dest[destOffset + j] = c0[j] + (x * diff[j]); } - - return out; - }; }, @@ -317,12 +325,14 @@ var PDFFunction = (function PDFFunctionClosure() { var encode = IR[3]; var fnsIR = IR[4]; var fns = []; + var tmpBuf = new Float32Array(1); for (var i = 0, ii = fnsIR.length; i < ii; i++) { fns.push(PDFFunction.fromIR(fnsIR[i])); } - return function constructStichedFromIRResult(args) { + return function constructStichedFromIRResult(src, srcOffset, + dest, destOffset) { var clip = function constructStichedFromIRClip(v, min, max) { if (v > max) { v = max; @@ -333,7 +343,7 @@ var PDFFunction = (function PDFFunctionClosure() { }; // clip to domain - var v = clip(args[0], domain[0], domain[1]); + var v = clip(src[srcOffset], domain[0], domain[1]); // calulate which bound the value is in for (var i = 0, ii = bounds.length; i < ii; ++i) { if (v < bounds[i]) { @@ -354,10 +364,10 @@ var PDFFunction = (function PDFFunctionClosure() { var rmin = encode[2 * i]; var rmax = encode[2 * i + 1]; - var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); + tmpBuf[0] = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); // call the appropriate function - return fns[i]([v2]); + fns[i](tmpBuf, 0, dest, destOffset); }; }, @@ -393,7 +403,7 @@ var PDFFunction = (function PDFFunctionClosure() { // subtraction, Math.max, and also contains 'var' and 'return' // statements. See the generation in the PostScriptCompiler below. /*jshint -W054 */ - return new Function('args', compiled); + return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled); } info('Unable to compile PS function'); @@ -408,22 +418,26 @@ var PDFFunction = (function PDFFunctionClosure() { // seen in our tests. var MAX_CACHE_SIZE = 2048 * 4; var cache_available = MAX_CACHE_SIZE; - return function constructPostScriptFromIRResult(args) { + var tmpBuf = new Float32Array(numInputs); + + return function constructPostScriptFromIRResult(src, srcOffset, + dest, destOffset) { var i, value; var key = ''; - var input = new Array(numInputs); + var input = tmpBuf; for (i = 0; i < numInputs; i++) { - value = args[i]; + value = src[srcOffset + i]; input[i] = value; key += value + '_'; } var cachedValue = cache[key]; if (cachedValue !== undefined) { - return cachedValue; + cachedValue.set(dest, destOffset); + return; } - var output = new Array(numOutputs); + var output = new Float32Array(numOutputs); var stack = evaluator.execute(input); var stackIndex = stack.length - numOutputs; for (i = 0; i < numOutputs; i++) { @@ -443,7 +457,7 @@ var PDFFunction = (function PDFFunctionClosure() { cache_available--; cache[key] = output; } - return output; + output.set(dest, destOffset); }; } }; @@ -466,7 +480,8 @@ function isPDFFunction(v) { var PostScriptStack = (function PostScriptStackClosure() { var MAX_STACK_SIZE = 100; function PostScriptStack(initialStack) { - this.stack = initialStack || []; + this.stack = !initialStack ? [] : + Array.prototype.slice.call(initialStack, 0); } PostScriptStack.prototype = { @@ -836,7 +851,7 @@ var PostScriptCompiler = (function PostScriptCompilerClosure() { ExpressionBuilderVisitor.prototype = { visitArgument: function (arg) { this.parts.push('Math.max(', arg.min, ', Math.min(', - arg.max, ', args[', arg.index, ']))'); + arg.max, ', src[srcOffset + ', arg.index, ']))'); }, visitVariable: function (variable) { this.parts.push('v', variable.index); @@ -1092,7 +1107,7 @@ var PostScriptCompiler = (function PostScriptCompilerClosure() { instruction.visit(statementBuilder); result.push(statementBuilder.toString()); }); - result.push('return [\n ' + stack.map(function (expr, i) { + stack.forEach(function (expr, i) { var statementBuilder = new ExpressionBuilderVisitor(); expr.visit(statementBuilder); var min = range[i * 2], max = range[i * 2 + 1]; @@ -1105,8 +1120,10 @@ var PostScriptCompiler = (function PostScriptCompilerClosure() { out.unshift('Math.min(', max, ', '); out.push(')'); } - return out.join(''); - }).join(',\n ') + '\n];'); + out.unshift('dest[destOffset + ', i, '] = '); + out.push(';'); + result.push(out.join('')); + }); return result.join('\n'); } }; diff --git a/src/core/pattern.js b/src/core/pattern.js index 2a320dfd8..2bb9c72b8 100644 --- a/src/core/pattern.js +++ b/src/core/pattern.js @@ -122,29 +122,7 @@ Shadings.RadialAxial = (function RadialAxialClosure() { this.extendEnd = extendEnd; var fnObj = dict.get('Function'); - var fn; - if (isArray(fnObj)) { - var fnArray = []; - for (var j = 0, jj = fnObj.length; j < jj; j++) { - var obj = xref.fetchIfRef(fnObj[j]); - if (!isPDFFunction(obj)) { - error('Invalid function'); - } - fnArray.push(PDFFunction.parse(xref, obj)); - } - fn = function radialAxialColorFunction(arg) { - var out = []; - for (var i = 0, ii = fnArray.length; i < ii; i++) { - out.push(fnArray[i](arg)[0]); - } - return out; - }; - } else { - if (!isPDFFunction(fnObj)) { - error('Invalid function'); - } - fn = PDFFunction.parse(xref, fnObj); - } + var fn = PDFFunction.parseArray(xref, fnObj); // 10 samples seems good enough for now, but probably won't work // if there are sharp color changes. Ideally, we would implement @@ -162,9 +140,12 @@ Shadings.RadialAxial = (function RadialAxialClosure() { return; } + var color = new Float32Array(cs.numComps), ratio = new Float32Array(1); var rgbColor; for (var i = t0; i <= t1; i += step) { - rgbColor = cs.getRgb(fn([i]), 0); + ratio[0] = i; + fn(ratio, 0, color, 0); + rgbColor = cs.getRgb(color, 0); var cssColor = Util.makeCssRgb(rgbColor); colorStops.push([(i - t0) / diff, cssColor]); } @@ -232,6 +213,12 @@ Shadings.Mesh = (function MeshClosure() { this.context = context; this.buffer = 0; this.bufferLength = 0; + + var numComps = context.numComps; + this.tmpCompsBuf = new Float32Array(numComps); + var csNumComps = context.colorSpace; + this.tmpCsCompsBuf = context.colorFn ? new Float32Array(csNumComps) : + this.tmpCompsBuf; } MeshStreamReader.prototype = { get hasData() { @@ -302,15 +289,16 @@ Shadings.Mesh = (function MeshClosure() { var scale = bitsPerComponent < 32 ? 1 / ((1 << bitsPerComponent) - 1) : 2.3283064365386963e-10; // 2 ^ -32 var decode = this.context.decode; - var components = []; + var components = this.tmpCompsBuf; for (var i = 0, j = 4; i < numComps; i++, j += 2) { var ci = this.readBits(bitsPerComponent); - components.push(ci * scale * (decode[j + 1] - decode[j]) + decode[j]); + components[i] = ci * scale * (decode[j + 1] - decode[j]) + decode[j]; } + var color = this.tmpCsCompsBuf; if (this.context.colorFn) { - components = this.context.colorFn(components); + this.context.colorFn(components, 0, color, 0); } - return this.context.colorSpace.getRgb(components, 0); + return this.context.colorSpace.getRgb(color, 0); } }; @@ -716,31 +704,7 @@ Shadings.Mesh = (function MeshClosure() { cs.getRgb(dict.get('Background'), 0) : null; var fnObj = dict.get('Function'); - var fn; - if (!fnObj) { - fn = null; - } else if (isArray(fnObj)) { - var fnArray = []; - for (var j = 0, jj = fnObj.length; j < jj; j++) { - var obj = xref.fetchIfRef(fnObj[j]); - if (!isPDFFunction(obj)) { - error('Invalid function'); - } - fnArray.push(PDFFunction.parse(xref, obj)); - } - fn = function radialAxialColorFunction(arg) { - var out = []; - for (var i = 0, ii = fnArray.length; i < ii; i++) { - out.push(fnArray[i](arg)[0]); - } - return out; - }; - } else { - if (!isPDFFunction(fnObj)) { - error('Invalid function'); - } - fn = PDFFunction.parse(xref, fnObj); - } + var fn = fnObj ? PDFFunction.parseArray(xref, fnObj) : null; this.coords = []; this.colors = []; diff --git a/test/unit/function_spec.js b/test/unit/function_spec.js index b55b01f52..77b7ba7b2 100644 --- a/test/unit/function_spec.js +++ b/test/unit/function_spec.js @@ -425,10 +425,13 @@ describe('function', function() { } else { expect(compiledCode).not.toBeNull(); /*jshint -W054 */ - var fn = new Function('args', compiledCode); + var fn = new Function('src', 'srcOffset', 'dest', 'destOffset', + compiledCode); for (var i = 0; i < samples.length; i++) { - var out = fn(samples[i].input); - expect(out).toMatchArray(samples[i].output); + var out = new Float32Array(samples[i].output.length); + fn(samples[i].input, 0, out, 0); + expect(Array.prototype.slice.call(out, 0)). + toMatchArray(samples[i].output); } } } @@ -475,11 +478,11 @@ describe('function', function() { check(['mul'], [0, 1], [0, 1], null); }); it('check compiled max', function() { - check(['dup', 0.6, 'gt', 7, 'jz', 'pop', 0.6], [0, 1], [0, 1], + check(['dup', 0.75, 'gt', 7, 'jz', 'pop', 0.75], [0, 1], [0, 1], [{input: [0.5], output: [0.5]}]); - check(['dup', 0.6, 'gt', 7, 'jz', 'pop', 0.6], [0, 1], [0, 1], - [{input: [1], output: [0.6]}]); - check(['dup', 0.6, 'gt', 5, 'jz', 'pop', 0.6], [0, 1], [0, 1], null); + check(['dup', 0.75, 'gt', 7, 'jz', 'pop', 0.75], [0, 1], [0, 1], + [{input: [1], output: [0.75]}]); + check(['dup', 0.75, 'gt', 5, 'jz', 'pop', 0.75], [0, 1], [0, 1], null); }); it('check pop/roll/index', function() { check([1, 'pop'], [0, 1], [0, 1], [{input: [0.5], output: [0.5]}]); @@ -496,24 +499,23 @@ describe('function', function() { it('check input boundaries', function () { check([], [0, 0.5], [0, 1], [{input: [1], output: [0.5]}]); check([], [0.5, 1], [0, 1], [{input: [0], output: [0.5]}]); - check(['dup'], [0.5, 0.6], [0, 1, 0, 1], + check(['dup'], [0.5, 0.75], [0, 1, 0, 1], [{input: [0], output: [0.5, 0.5]}]); check([], [100, 1001], [0, 10000], [{input: [1000], output: [1000]}]); }); it('check output boundaries', function () { check([], [0, 1], [0, 0.5], [{input: [1], output: [0.5]}]); check([], [0, 1], [0.5, 1], [{input: [0], output: [0.5]}]); - check(['dup'], [0, 1], [0.5, 1, 0.6, 1], - [{input: [0], output: [0.5, 0.6]}]); + check(['dup'], [0, 1], [0.5, 1, 0.75, 1], + [{input: [0], output: [0.5, 0.75]}]); check([], [0, 10000], [100, 1001], [{input: [1000], output: [1000]}]); }); it('compile optimized', function () { var compiler = new PostScriptCompiler(); var code = [0, 'add', 1, 1, 3, -1, 'roll', 'sub', 'sub', 1, 'mul']; var compiledCode = compiler.compile(code, [0, 1], [0, 1]); - expect(compiledCode).toEqual('return [\n' + - ' Math.max(0, Math.min(1, args[0]))\n' + - '];'); + expect(compiledCode).toEqual( + 'dest[destOffset + 0] = Math.max(0, Math.min(1, src[srcOffset + 0]));'); }); });