Skip to content

Commit f1ce40c

Browse files
committed
png 32 and png 24 support added to addImage
png 32 and png 24 support added to addImage partial png 8, but needs looking at as there are issues with transparency… and colour ArrayBuffer and ArrayBufferView/TypedArray support added to addImage
1 parent 8077f26 commit f1ce40c

1 file changed

Lines changed: 268 additions & 42 deletions

File tree

jspdf.plugin.addimage.js

Lines changed: 268 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@
3030
;(function(jsPDFAPI) {
3131
'use strict'
3232

33-
var namespace = 'addImage_'
33+
var namespace = 'addImage_',
34+
supported_image_types = ['jpg', 'jpeg', 'png'],
35+
color_spaces = ['DeviceRGB', 'DeviceGray', 'DeviceCMYK', 'Indexed'],
36+
filter_methods = ['DCTDecode', 'FlateDecode', 'LZWDecode'];
37+
3438

3539
// takes a string imgData containing the raw bytes of
3640
// a jpeg image and returns [width, height]
@@ -48,7 +52,7 @@ var getJpegSize = function(imgData) {
4852
!imgData.charCodeAt(8) === 'I'.charCodeAt(0) ||
4953
!imgData.charCodeAt(9) === 'F'.charCodeAt(0) ||
5054
!imgData.charCodeAt(10) === 0x00) {
51-
throw new Error('getJpegSize requires a binary jpeg file')
55+
throw new Error('getJpegSize requires a binary string jpeg file')
5256
}
5357
var blockLength = imgData.charCodeAt(4)*256 + imgData.charCodeAt(5);
5458
var i = 4, len = imgData.length;
@@ -76,6 +80,7 @@ var getJpegSize = function(imgData) {
7680
}
7781
// Image functionality ported from pdf.js
7882
, putImage = function(img) {
83+
//console.log(img);
7984
var objectNumber = this.internal.newObject()
8085
, out = this.internal.write
8186
, putStream = this.internal.putStream
@@ -106,7 +111,7 @@ var getJpegSize = function(imgData) {
106111
if ('trns' in img && img['trns'].constructor == Array) {
107112
var trns = '';
108113
for ( var i = 0; i < img['trns'].length; i++) {
109-
trns += (img[trns][i] + ' ' + img['trns'][i] + ' ');
114+
trns += (img['trns'][i] + ' ' + img['trns'][i] + ' ');
110115
out('/Mask [' + trns + ']');
111116
}
112117
}
@@ -118,6 +123,26 @@ var getJpegSize = function(imgData) {
118123
putStream(img['data']);
119124

120125
out('endobj');
126+
127+
// Soft mask
128+
if ('smask' in img) {
129+
var dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' + img['w'];
130+
var smask = {'w': img['w'], 'h': img['h'], 'cs': 'DeviceGray', 'bpc': 8, 'dp': dp, 'data': img['smask']};
131+
if ('f' in img)
132+
smask.f = img['f'];
133+
putImage.call(this, smask);
134+
}
135+
136+
//Palette
137+
if (img['cs'] === 'Indexed') {
138+
139+
this.internal.newObject();
140+
//out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
141+
//putStream(zlib.compress(img['pal']));
142+
out('<< /Length ' + img['pal'].length + '>>');
143+
putStream(img['pal']);
144+
out('endobj');
145+
}
121146
}
122147
, putResourcesCallback = function() {
123148
var images = this.internal.collections[namespace + 'images']
@@ -139,24 +164,118 @@ var getJpegSize = function(imgData) {
139164
)
140165
}
141166
}
167+
, extractBase64Info = function(dataURL) {
168+
return /^(data:image\/([\w]+?);base64,)(.+?)$/g.exec(dataURL);
169+
}
170+
, supportsArrayBuffer = function() {
171+
return typeof ArrayBuffer == 'function';
172+
}
173+
, isArrayBuffer = function(object) {
174+
if(!supportsArrayBuffer())
175+
return false;
176+
return object instanceof ArrayBuffer;
177+
}
178+
, isArrayBufferView = function(object) {
179+
if(!supportsArrayBuffer())
180+
return false;
181+
return (object instanceof Int8Array ||
182+
object instanceof Uint8Array ||
183+
object instanceof Uint8ClampedArray ||
184+
object instanceof Int16Array ||
185+
object instanceof Uint16Array ||
186+
object instanceof Int32Array ||
187+
object instanceof Uint32Array ||
188+
object instanceof Float32Array ||
189+
object instanceof Float64Array );
190+
}
191+
, binaryStringToUint8Array = function(binary_string) {
192+
var len = binary_string.length;
193+
var bytes = new Uint8Array( len );
194+
for (var i = 0; i < len; i++) {
195+
bytes[i] = binary_string.charCodeAt(i);
196+
}
197+
return bytes;
198+
}
199+
, arrayBufferToBinaryString = function( array_buffer ) {
200+
/*var binary_string = '';
201+
//var bytes = new Uint8Array( array_buffer );
202+
var bytes = array_buffer;
203+
var len = bytes.byteLength;
204+
for (var i = 0; i < len; i++) {
205+
binary_string += String.fromCharCode( bytes[ i ] );
206+
}
207+
return binary_string;*/
208+
209+
if(isArrayBufferView())
210+
array_buffer = array_buffer.buffer;
211+
212+
var base64 = base64ArrayBuffer(array_buffer);
213+
214+
return window.atob(base64);
215+
}
216+
, createImageInfo = function(data, wd, ht, cs, bpc, f, imageIndex, alias, dp, trns, pal, smask) {
217+
var info = {
218+
alias:alias,
219+
w : wd,
220+
h : ht,
221+
cs : cs,
222+
bpc : bpc,
223+
i : imageIndex,
224+
data : data
225+
// n: objectNumber will be added by putImage code
226+
};
227+
228+
if(f)
229+
info.f = f;
230+
231+
if(dp)
232+
info.dp = dp;
233+
234+
if(trns)
235+
info.trns = trns;
236+
237+
if(pal)
238+
info.pal = pal;
239+
240+
if(smask)
241+
info.smask = smask;
242+
243+
return info;
244+
};
142245

143246
jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias) {
144247
'use strict'
145248
var images = this.internal.collections[namespace + 'images'],
146-
cached_info;
249+
cached_info,
250+
binaryStringData;
147251

148252
if(typeof format === 'number') {
149253
var tmp = h;
150254
h = w;
151255
w = y;
152256
y = x;
153257
x = format;
154-
format = tmp || 'JPEG';
258+
format = tmp || 'jpeg';
155259
}
260+
156261
if(typeof alias === 'undefined') {
157262
// TODO: Alias dynamic generation from imageData's checksum/hash
158263
}
159264

265+
if (typeof imageData === 'object' && imageData.nodeType === 1) {
266+
var canvas = document.createElement('canvas');
267+
canvas.width = imageData.clientWidth || imageData.width;
268+
canvas.height = imageData.clientHeight || imageData.height;
269+
270+
var ctx = canvas.getContext('2d');
271+
if (!ctx) {
272+
throw ('addImage requires canvas to be supported by browser.');
273+
}
274+
ctx.drawImage(imageData, 0, 0, canvas.width, canvas.height);
275+
imageData = canvas.toDataURL();
276+
format = "png";
277+
}
278+
160279
if(typeof imageData === 'string') {
161280
if(imageData.charCodeAt(0) !== 0xff && imageData.substr(0,5) !== 'data:') {
162281
// This is neither raw jpeg-data nor a data uri; alias?
@@ -171,36 +290,35 @@ jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias) {
171290
}
172291
}
173292

174-
} else if(imageData.substr(0,14) === 'data:image/jpg') {
175-
imageData = imageData.replace('data:image/jpg','data:image/jpeg');
293+
} else {
294+
295+
var base64Info = extractBase64Info(imageData);
296+
297+
if(base64Info) {
298+
299+
format = base64Info[2];
300+
imageData = atob(base64Info[3]);//convert to binary string
301+
302+
/*
303+
* need to test if it's more efficent to convert all binary strings
304+
* to TypedArray - or should we just leave and process as string?
305+
*/
306+
if(supportsArrayBuffer()) {
307+
binaryStringData = imageData;
308+
imageData = binaryStringToUint8Array(imageData);
309+
}
310+
311+
}
176312
}
177313
}
178-
if (typeof imageData === 'object' && imageData.nodeType === 1) {
179-
var canvas = document.createElement('canvas');
180-
canvas.width = imageData.clientWidth || imageData.width;
181-
canvas.height = imageData.clientHeight || imageData.height;
182-
183-
var ctx = canvas.getContext('2d');
184-
if (!ctx) {
185-
throw ('addImage requires canvas to be supported by browser.');
186-
}
187-
ctx.drawImage(imageData, 0, 0, canvas.width, canvas.height);
188-
imageData = canvas.toDataURL('image/jpeg');
189-
format = "JPEG";
190-
}
191-
if (format.toUpperCase() !== 'JPEG') {
192-
throw new Error('addImage currently only supports format \'JPEG\', not \''+format+'\'');
193-
}
314+
315+
if( supported_image_types.indexOf(format.toLowerCase()) == -1 )
316+
throw new Error('addImage currently only supports formats ' + supported_image_types + ', not \''+format+'\'');
194317

195318
var imageIndex
196319
, coord = this.internal.getCoordinateString
197320
, vcoord = this.internal.getVerticalCoordinateString;
198321

199-
// Detect if the imageData is raw binary or Data URL
200-
if (imageData.substring(0, 23) === 'data:image/jpeg;base64,') {
201-
imageData = atob(imageData.replace('data:image/jpeg;base64,', ''));
202-
}
203-
204322
if (images){
205323
// this is NOT the first time this method is ran on this instance of jsPDF object.
206324
imageIndex = Object.keys ?
@@ -218,20 +336,128 @@ jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias) {
218336
this.internal.events.subscribe('putXobjectDict', putXObjectsDictCallback)
219337
}
220338

221-
var info = cached_info || (function(dims) {
222-
return images[imageIndex] = {
223-
alias:alias,
224-
w : dims[0],
225-
h : dims[1],
226-
cs : 'DeviceRGB',
227-
bpc : 8,
228-
f : 'DCTDecode',
229-
i : imageIndex,
230-
data : imageData
231-
// n: objectNumber will be added by putImage code
232-
};
233-
})(getJpegSize(imageData));
234-
339+
var info = cached_info;
340+
341+
if(!info) {
342+
343+
var img,
344+
dims,
345+
dp,
346+
trns,
347+
colorSpace = color_spaces[0],
348+
filter = filter_methods[0],
349+
bpc = 8,
350+
colors,
351+
pal,
352+
smask;
353+
354+
format = format.toLocaleLowerCase();
355+
356+
/*
357+
* we have a binary string
358+
*/
359+
if(typeof imageData === 'string') {
360+
361+
if(['jpeg', 'jpg'].indexOf(format) !== -1) {
362+
363+
dims = getJpegSize(imageData);
364+
info = createImageInfo(imageData, dims[0], dims[1], colorSpace, bpc, filter, imageIndex, alias);
365+
}
366+
367+
if(format === 'png') {
368+
369+
}
370+
}
371+
372+
if(isArrayBuffer(imageData))
373+
imageData = new Uint8Array(imageData);
374+
375+
if(isArrayBufferView(imageData)) {
376+
377+
if(['jpeg', 'jpg'].indexOf(format) !== -1) {
378+
img = new JpegImage();
379+
img.parse(imageData);
380+
381+
/*
382+
* check if already have a stored binary string rep
383+
*/
384+
imageData = binaryStringData || arrayBufferToBinaryString(imageData);
385+
386+
info = createImageInfo(imageData, img.width, img.height, colorSpace, bpc, filter, imageIndex, alias);
387+
}
388+
389+
if(format === 'png') {
390+
391+
img = new PNG(imageData);
392+
imageData = img.imgData;
393+
bpc = img.bits;
394+
colorSpace = img.colorSpace;
395+
colors = img.colors;
396+
filter = filter_methods[1];//FlateDecode as png pixels are compressed
397+
398+
/*
399+
* we need to extract alpha layer and add it as a smask
400+
*/
401+
if(img.colorType === 6 &&
402+
img.pixelBitlength === 32) {
403+
404+
var //pixels = new DataView(img.decode().buffer),
405+
pixels = new Uint32Array(img.decode().buffer),
406+
//len = pixels.byteLength / 4,
407+
len = pixels.length,
408+
colorData = new Uint8Array(len * 3),
409+
transData = new Uint8Array(len),
410+
pixel,
411+
i = 0,
412+
n = 0;
413+
414+
for(; i < len; i++) {
415+
//pixel = pixels.getInt32(i);
416+
pixel = pixels[i];
417+
418+
colorData[n++] = ( pixel >> 0 ) & 0xff;
419+
colorData[n++] = ( pixel >> 8 ) & 0xff;
420+
colorData[n++] = ( pixel >> 16 ) & 0xff;
421+
422+
transData[i] = ( pixel >> 24 ) & 0xff;
423+
}
424+
425+
imageData = colorData;
426+
smask = arrayBufferToBinaryString(transData);
427+
filter = null;
428+
}
429+
430+
if(filter === filter_methods[1])
431+
dp = '/Predictor 15 /Colors '+ colors +' /BitsPerComponent '+ bpc +' /Columns '+ img.width;
432+
433+
if(img.palette && img.palette.length > 0)
434+
pal = img.palette;
435+
436+
if(img.transparency.indexed)
437+
trns = img.transparency.indexed;
438+
439+
info = createImageInfo(arrayBufferToBinaryString(imageData),
440+
img.width,
441+
img.height,
442+
colorSpace,
443+
bpc,
444+
filter,
445+
imageIndex,
446+
alias,
447+
dp,
448+
trns,
449+
pal,
450+
smask);
451+
}
452+
}
453+
454+
}
455+
456+
if( !info )
457+
throw new Error('Something went wrong, theres no image info');
458+
459+
images[imageIndex] = info;
460+
235461
if (!w && !h) {
236462
w = -96;
237463
h = -96;

0 commit comments

Comments
 (0)