/**
* @license
*
* Copyright (c) 2014 James Robb, https://github.com/jamesbrobb
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/
import { jsPDF } from "../jspdf.js";
import { zlibSync } from "../libs/fflate.js";
import { PNG } from "../libs/png.js";
/**
* jsPDF PNG PlugIn
* @name png_support
* @module
*/
(function(jsPDFAPI) {
"use strict";
/*
* @see http://www.w3.org/TR/PNG-Chunks.html
*
Color Allowed Interpretation
Type Bit Depths
0 1,2,4,8,16 Each pixel is a grayscale sample.
2 8,16 Each pixel is an R,G,B triple.
3 1,2,4,8 Each pixel is a palette index;
a PLTE chunk must appear.
4 8,16 Each pixel is a grayscale sample,
followed by an alpha sample.
6 8,16 Each pixel is an R,G,B triple,
followed by an alpha sample.
*/
/*
* PNG filter method types
*
* @see http://www.w3.org/TR/PNG-Filters.html
* @see http://www.libpng.org/pub/png/book/chapter09.html
*
* This is what the value 'Predictor' in decode params relates to
*
* 15 is "optimal prediction", which means the prediction algorithm can change from line to line.
* In that case, you actually have to read the first byte off each line for the prediction algorthim (which should be 0-4, corresponding to PDF 10-14) and select the appropriate unprediction algorithm based on that byte.
*
0 None
1 Sub
2 Up
3 Average
4 Paeth
*/
var canCompress = function(value) {
return value !== jsPDFAPI.image_compression.NONE && hasCompressionJS();
};
var hasCompressionJS = function() {
return typeof zlibSync === "function";
};
var compressBytes = function(bytes, lineLength, colorsPerPixel, compression) {
var level = 4;
var filter_method = filterUp;
switch (compression) {
case jsPDFAPI.image_compression.FAST:
level = 1;
filter_method = filterSub;
break;
case jsPDFAPI.image_compression.MEDIUM:
level = 6;
filter_method = filterAverage;
break;
case jsPDFAPI.image_compression.SLOW:
level = 9;
filter_method = filterPaeth;
break;
}
bytes = applyPngFilterMethod(
bytes,
lineLength,
colorsPerPixel,
filter_method
);
var dat = zlibSync(bytes, { level: level });
return jsPDFAPI.__addimage__.arrayBufferToBinaryString(dat);
};
var applyPngFilterMethod = function(
bytes,
lineLength,
colorsPerPixel,
filter_method
) {
var lines = bytes.length / lineLength,
result = new Uint8Array(bytes.length + lines),
filter_methods = getFilterMethods(),
line,
prevLine,
offset;
for (var i = 0; i < lines; i += 1) {
offset = i * lineLength;
line = bytes.subarray(offset, offset + lineLength);
if (filter_method) {
result.set(filter_method(line, colorsPerPixel, prevLine), offset + i);
} else {
var len = filter_methods.length,
results = [];
for (var j; j < len; j += 1) {
results[j] = filter_methods[j](line, colorsPerPixel, prevLine);
}
var ind = getIndexOfSmallestSum(results.concat());
result.set(results[ind], offset + i);
}
prevLine = line;
}
return result;
};
var filterNone = function(line) {
/*var result = new Uint8Array(line.length + 1);
result[0] = 0;
result.set(line, 1);*/
var result = Array.apply([], line);
result.unshift(0);
return result;
};
var filterSub = function(line, colorsPerPixel) {
var result = [],
len = line.length,
left;
result[0] = 1;
for (var i = 0; i < len; i += 1) {
left = line[i - colorsPerPixel] || 0;
result[i + 1] = (line[i] - left + 0x0100) & 0xff;
}
return result;
};
var filterUp = function(line, colorsPerPixel, prevLine) {
var result = [],
len = line.length,
up;
result[0] = 2;
for (var i = 0; i < len; i += 1) {
up = (prevLine && prevLine[i]) || 0;
result[i + 1] = (line[i] - up + 0x0100) & 0xff;
}
return result;
};
var filterAverage = function(line, colorsPerPixel, prevLine) {
var result = [],
len = line.length,
left,
up;
result[0] = 3;
for (var i = 0; i < len; i += 1) {
left = line[i - colorsPerPixel] || 0;
up = (prevLine && prevLine[i]) || 0;
result[i + 1] = (line[i] + 0x0100 - ((left + up) >>> 1)) & 0xff;
}
return result;
};
var filterPaeth = function(line, colorsPerPixel, prevLine) {
var result = [],
len = line.length,
left,
up,
upLeft,
paeth;
result[0] = 4;
for (var i = 0; i < len; i += 1) {
left = line[i - colorsPerPixel] || 0;
up = (prevLine && prevLine[i]) || 0;
upLeft = (prevLine && prevLine[i - colorsPerPixel]) || 0;
paeth = paethPredictor(left, up, upLeft);
result[i + 1] = (line[i] - paeth + 0x0100) & 0xff;
}
return result;
};
var paethPredictor = function(left, up, upLeft) {
if (left === up && up === upLeft) {
return left;
}
var pLeft = Math.abs(up - upLeft),
pUp = Math.abs(left - upLeft),
pUpLeft = Math.abs(left + up - upLeft - upLeft);
return pLeft <= pUp && pLeft <= pUpLeft
? left
: pUp <= pUpLeft
? up
: upLeft;
};
var getFilterMethods = function() {
return [filterNone, filterSub, filterUp, filterAverage, filterPaeth];
};
var getIndexOfSmallestSum = function(arrays) {
var sum = arrays.map(function(value) {
return value.reduce(function(pv, cv) {
return pv + Math.abs(cv);
}, 0);
});
return sum.indexOf(Math.min.apply(null, sum));
};
var getPredictorFromCompression = function(compression) {
var predictor;
switch (compression) {
case jsPDFAPI.image_compression.FAST:
predictor = 11;
break;
case jsPDFAPI.image_compression.MEDIUM:
predictor = 13;
break;
case jsPDFAPI.image_compression.SLOW:
predictor = 14;
break;
default:
predictor = 12;
break;
}
return predictor;
};
/**
* @name processPNG
* @function
* @ignore
*/
jsPDFAPI.processPNG = function(imageData, index, alias, compression) {
"use strict";
var colorSpace,
filter = this.decode.FLATE_DECODE,
bitsPerComponent,
image,
decodeParameters = "",
trns,
colors,
pal,
smask,
pixels,
len,
alphaData,
imgData,
hasColors,
pixel,
i,
n;
if (this.__addimage__.isArrayBuffer(imageData))
imageData = new Uint8Array(imageData);
if (this.__addimage__.isArrayBufferView(imageData)) {
image = new PNG(imageData);
imageData = image.imgData;
bitsPerComponent = image.bits;
colorSpace = image.colorSpace;
colors = image.colors;
/*
* colorType 6 - Each pixel is an R,G,B triple, followed by an alpha sample.
*
* colorType 4 - Each pixel is a grayscale sample, followed by an alpha sample.
*
* Extract alpha to create two separate images, using the alpha as a sMask
*/
if ([4, 6].indexOf(image.colorType) !== -1) {
/*
* processes 8 bit RGBA and grayscale + alpha images
*/
if (image.bits === 8) {
pixels =
image.pixelBitlength == 32
? new Uint32Array(image.decodePixels().buffer)
: image.pixelBitlength == 16
? new Uint16Array(image.decodePixels().buffer)
: new Uint8Array(image.decodePixels().buffer);
len = pixels.length;
imgData = new Uint8Array(len * image.colors);
alphaData = new Uint8Array(len);
var pDiff = image.pixelBitlength - image.bits;
i = 0;
n = 0;
var pbl;
for (; i < len; i++) {
pixel = pixels[i];
pbl = 0;
while (pbl < pDiff) {
imgData[n++] = (pixel >>> pbl) & 0xff;
pbl = pbl + image.bits;
}
alphaData[i] = (pixel >>> pbl) & 0xff;
}
}
/*
* processes 16 bit RGBA and grayscale + alpha images
*/
if (image.bits === 16) {
pixels = new Uint32Array(image.decodePixels().buffer);
len = pixels.length;
imgData = new Uint8Array(
len * (32 / image.pixelBitlength) * image.colors
);
alphaData = new Uint8Array(len * (32 / image.pixelBitlength));
hasColors = image.colors > 1;
i = 0;
n = 0;
var a = 0;
while (i < len) {
pixel = pixels[i++];
imgData[n++] = (pixel >>> 0) & 0xff;
if (hasColors) {
imgData[n++] = (pixel >>> 16) & 0xff;
pixel = pixels[i++];
imgData[n++] = (pixel >>> 0) & 0xff;
}
alphaData[a++] = (pixel >>> 16) & 0xff;
}
bitsPerComponent = 8;
}
if (canCompress(compression)) {
imageData = compressBytes(
imgData,
image.width * image.colors,
image.colors,
compression
);
smask = compressBytes(alphaData, image.width, 1, compression);
} else {
imageData = imgData;
smask = alphaData;
filter = undefined;
}
}
/*
* Indexed png. Each pixel is a palette index.
*/
if (image.colorType === 3) {
colorSpace = this.color_spaces.INDEXED;
pal = image.palette;
if (image.transparency.indexed) {
var trans = image.transparency.indexed;
var total = 0;
i = 0;
len = trans.length;
for (; i < len; ++i) {
total += trans[i];
}
total = total / 255;
/*
* a single color is specified as 100% transparent (0),
* so we set trns to use a /Mask with that index
*/
if (total === len - 1 && trans.indexOf(0) !== -1) {
trns = [trans.indexOf(0)];
/*
* there's more than one colour within the palette that specifies
* a transparency value less than 255, so we unroll the pixels to create an image sMask
*/
} else if (total !== len) {
pixels = image.decodePixels();
alphaData = new Uint8Array(pixels.length);
i = 0;
len = pixels.length;
for (; i < len; i++) {
alphaData[i] = trans[pixels[i]];
}
smask = compressBytes(alphaData, image.width, 1);
}
}
}
var predictor = getPredictorFromCompression(compression);
if (filter === this.decode.FLATE_DECODE) {
decodeParameters = "/Predictor " + predictor + " ";
}
decodeParameters +=
"/Colors " +
colors +
" /BitsPerComponent " +
bitsPerComponent +
" /Columns " +
image.width;
if (
this.__addimage__.isArrayBuffer(imageData) ||
this.__addimage__.isArrayBufferView(imageData)
) {
imageData = this.__addimage__.arrayBufferToBinaryString(imageData);
}
if (
(smask && this.__addimage__.isArrayBuffer(smask)) ||
this.__addimage__.isArrayBufferView(smask)
) {
smask = this.__addimage__.arrayBufferToBinaryString(smask);
}
return {
alias: alias,
data: imageData,
index: index,
filter: filter,
decodeParameters: decodeParameters,
transparency: trns,
palette: pal,
sMask: smask,
predictor: predictor,
width: image.width,
height: image.height,
bitsPerComponent: bitsPerComponent,
colorSpace: colorSpace
};
}
};
})(jsPDF.API);