Skip to content

Commit b6ed102

Browse files
PanturaanttipalolaHackbrettXXX
authored
Feature/add rgba array support (parallax#3124)
Co-authored-by: Antti Palola <[email protected]> Co-authored-by: Lukas Hollaender <[email protected]>
1 parent 54a0ce9 commit b6ed102

File tree

9 files changed

+245
-5
lines changed

9 files changed

+245
-5
lines changed

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import "./modules/png_support.js";
1919
import "./modules/gif_support.js";
2020
import "./modules/bmp_support.js";
2121
import "./modules/webp_support.js";
22+
import "./modules/rgba_support.js";
2223
import "./modules/setlanguage.js";
2324
import "./modules/split_text_to_size.js";
2425
import "./modules/standard_fonts_metrics.js";

src/modules/addimage.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ import { atob, btoa } from "../libs/AtobBtoa.js";
139139
var compareResult;
140140
var fileType;
141141

142+
if (
143+
fallbackFormat === "RGBA" ||
144+
(imageData.data !== undefined &&
145+
imageData.data instanceof Uint8ClampedArray &&
146+
"height" in imageData &&
147+
"width" in imageData)
148+
) {
149+
return "RGBA";
150+
}
151+
142152
if (isArrayBufferView(imageData)) {
143153
for (fileType in imageFileTypeHeaders) {
144154
headerSchemata = imageFileTypeHeaders[fileType];
@@ -351,6 +361,8 @@ import { atob, btoa } from "../libs/AtobBtoa.js";
351361
var generateAliasFromImageData = function(imageData) {
352362
if (typeof imageData === "string" || isArrayBufferView(imageData)) {
353363
return sHashCode(imageData);
364+
} else if (isArrayBufferView(imageData.data)) {
365+
return sHashCode(imageData.data);
354366
}
355367

356368
return null;
@@ -759,13 +771,22 @@ import { atob, btoa } from "../libs/AtobBtoa.js";
759771
return out;
760772
});
761773

774+
/**
775+
* Possible parameter for addImage, an RGBA buffer with size.
776+
*
777+
* @typedef {Object} RGBAData
778+
* @property {Uint8ClampedArray} data - Single dimensional array of RGBA values. For example from canvas getImageData.
779+
* @property {number} width - Image width as the data does not carry this information in itself.
780+
* @property {number} height - Image height as the data does not carry this information in itself.
781+
*/
782+
762783
/**
763784
* Adds an Image to the PDF.
764785
*
765786
* @name addImage
766787
* @public
767788
* @function
768-
* @param {string|HTMLImageElement|HTMLCanvasElement|Uint8Array} imageData imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement
789+
* @param {string|HTMLImageElement|HTMLCanvasElement|Uint8Array|RGBAData} imageData imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement or object containing RGBA array (like output from canvas.getImageData).
769790
* @param {string} format format of file if filetype-recognition fails or in case of a Canvas-Element needs to be specified (default for Canvas is JPEG), e.g. 'JPEG', 'PNG', 'WEBP'
770791
* @param {number} x x Coordinate (in units declared at inception of PDF document) against left edge of the page
771792
* @param {number} y y Coordinate (in units declared at inception of PDF document) against upper edge of the page
@@ -889,7 +910,7 @@ import { atob, btoa } from "../libs/AtobBtoa.js";
889910
if (!result) {
890911
if (supportsArrayBuffer()) {
891912
// no need to convert if imageData is already uint8array
892-
if (!(imageData instanceof Uint8Array)) {
913+
if (!(imageData instanceof Uint8Array) && format !== "RGBA") {
893914
dataAsBinaryString = imageData;
894915
imageData = binaryStringToUint8Array(imageData);
895916
}

src/modules/rgba_support.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* @license
3+
*
4+
* Copyright (c) 2021 Antti Palola, https://github.com/Pantura
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining
7+
* a copy of this software and associated documentation files (the
8+
* "Software"), to deal in the Software without restriction, including
9+
* without limitation the rights to use, copy, modify, merge, publish,
10+
* distribute, sublicense, and/or sell copies of the Software, and to
11+
* permit persons to whom the Software is furnished to do so, subject to
12+
* the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be
15+
* included in all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24+
* ====================================================================
25+
*/
26+
27+
import { jsPDF } from "../jspdf.js";
28+
29+
/**
30+
* jsPDF RGBA array PlugIn
31+
* @name rgba_support
32+
* @module
33+
*/
34+
(function(jsPDFAPI) {
35+
"use strict";
36+
37+
/**
38+
* @name processRGBA
39+
* @function
40+
*
41+
* Process RGBA Array. This is a one-dimension array with pixel data [red, green, blue, alpha, red, green, ...].
42+
* RGBA array data can be obtained from DOM canvas getImageData.
43+
* @ignore
44+
*/
45+
jsPDFAPI.processRGBA = function(imageData, index, alias) {
46+
"use strict";
47+
48+
var imagePixels = imageData.data;
49+
var length = imagePixels.length;
50+
// jsPDF takes alpha data separately so extract that.
51+
var rgbOut = new Uint8Array((length / 4) * 3);
52+
var alphaOut = new Uint8Array(length / 4);
53+
var outIndex = 0;
54+
var alphaIndex = 0;
55+
56+
for (var i = 0; i < length; i += 4) {
57+
var r = imagePixels[i];
58+
var g = imagePixels[i + 1];
59+
var b = imagePixels[i + 2];
60+
var alpha = imagePixels[i + 3];
61+
rgbOut[outIndex++] = r;
62+
rgbOut[outIndex++] = g;
63+
rgbOut[outIndex++] = b;
64+
alphaOut[alphaIndex++] = alpha;
65+
}
66+
67+
var rgbData = this.__addimage__.arrayBufferToBinaryString(rgbOut);
68+
var alphaData = this.__addimage__.arrayBufferToBinaryString(alphaOut);
69+
70+
return {
71+
alpha: alphaData,
72+
data: rgbData,
73+
index: index,
74+
alias: alias,
75+
colorSpace: "DeviceRGB",
76+
bitsPerComponent: 8,
77+
width: imageData.width,
78+
height: imageData.height
79+
};
80+
};
81+
})(jsPDF.API);

test/reference/blackpixel_rgba.pdf

3.15 KB
Binary file not shown.

test/reference/rgba.pdf

36.1 KB
Binary file not shown.

test/reference/rgba_alpha.pdf

29.5 KB
Binary file not shown.

test/specs/rgba.spec.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/* global jsPDF */
2+
/**
3+
* Standard spec tests
4+
*/
5+
6+
describe("Module: RGBASupport", () => {
7+
beforeAll(loadGlobals);
8+
it("black pixel", () => {
9+
var blackpixel = new Uint8ClampedArray([0, 0, 0, 255]);
10+
var blackpixelData = {
11+
data: blackpixel,
12+
width: 1,
13+
height: 1
14+
};
15+
16+
const doc = new jsPDF({
17+
orientation: "p",
18+
unit: "pt",
19+
format: "a4",
20+
floatPrecision: 2
21+
});
22+
doc.addImage(blackpixelData, "RGBA", 15, 40, 1, 1);
23+
24+
comparePdf(doc.output(), "blackpixel_rgba.pdf", "addimage");
25+
});
26+
27+
it("without format", () => {
28+
var blackpixel = new Uint8ClampedArray([0, 0, 0, 255]);
29+
var blackpixelData = {
30+
data: blackpixel,
31+
width: 1,
32+
height: 1
33+
};
34+
35+
const doc = new jsPDF({
36+
orientation: "p",
37+
unit: "pt",
38+
format: "a4",
39+
floatPrecision: 2
40+
});
41+
doc.addImage(blackpixelData, "RGBA", 15, 40, 1, 1);
42+
43+
comparePdf(doc.output(), "blackpixel_rgba.pdf", "addimage");
44+
});
45+
46+
if (
47+
(typeof isNode === "undefined" || !isNode) &&
48+
navigator.userAgent.indexOf("Chrome") >= 0
49+
) {
50+
it("from canvas", () => {
51+
const c = document.createElement("canvas");
52+
const ctx = c.getContext("2d");
53+
ctx.fillStyle = "#FF6600";
54+
ctx.fillRect(0, 0, 150, 75);
55+
const dataFromCanvas = ctx.getImageData(0, 0, 150, 75);
56+
const doc = new jsPDF({
57+
orientation: "p",
58+
unit: "pt",
59+
format: "a4",
60+
floatPrecision: 2
61+
});
62+
doc.addImage(
63+
dataFromCanvas,
64+
"RGBA",
65+
100,
66+
200,
67+
280,
68+
210,
69+
undefined,
70+
undefined
71+
);
72+
73+
comparePdf(doc.output(), "rgba.pdf", "addimage");
74+
});
75+
76+
it("with alpha", () => {
77+
const c = document.createElement("canvas");
78+
const ctx = c.getContext("2d");
79+
ctx.fillStyle = "#FFFFFF";
80+
ctx.fillRect(0, 0, 150, 60);
81+
ctx.fillStyle = "#AA00FF77";
82+
ctx.fillRect(10, 10, 130, 40);
83+
const dataFromCanvas = ctx.getImageData(0, 0, 150, 60);
84+
85+
const doc = new jsPDF({
86+
orientation: "p",
87+
unit: "px",
88+
format: "a4",
89+
floatPrecision: 2
90+
});
91+
doc.addImage(dataFromCanvas, 10, 10, 150, 60);
92+
93+
comparePdf(doc.output(), "rgba_alpha.pdf", "addimage");
94+
});
95+
}
96+
});

types/index.d.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,12 @@ declare module "jspdf" {
499499
| "DeviceN";
500500

501501
export interface ImageOptions {
502-
imageData: string | HTMLImageElement | HTMLCanvasElement | Uint8Array;
502+
imageData:
503+
| string
504+
| HTMLImageElement
505+
| HTMLCanvasElement
506+
| Uint8Array
507+
| RGBAData;
503508
x: number;
504509
y: number;
505510
width: number;
@@ -646,6 +651,13 @@ declare module "jspdf" {
646651
yStep?: number;
647652
}
648653

654+
// Single dimensional array of RGBA values. For example from canvas getImageData.
655+
export interface RGBAData {
656+
data: Uint8ClampedArray;
657+
width: number;
658+
height: number;
659+
}
660+
649661
export interface PubSub {
650662
subscribe(
651663
topic: string,
@@ -920,7 +932,12 @@ declare module "jspdf" {
920932

921933
// jsPDF plugin: addImage
922934
addImage(
923-
imageData: string | HTMLImageElement | HTMLCanvasElement | Uint8Array,
935+
imageData:
936+
| string
937+
| HTMLImageElement
938+
| HTMLCanvasElement
939+
| Uint8Array
940+
| RGBAData,
924941
format: string,
925942
x: number,
926943
y: number,
@@ -931,7 +948,12 @@ declare module "jspdf" {
931948
rotation?: number
932949
): jsPDF;
933950
addImage(
934-
imageData: string | HTMLImageElement | HTMLCanvasElement | Uint8Array,
951+
imageData:
952+
| string
953+
| HTMLImageElement
954+
| HTMLCanvasElement
955+
| Uint8Array
956+
| RGBAData,
935957
x: number,
936958
y: number,
937959
w: number,

types/jspdf-tests.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,3 +649,22 @@ function test_nullStyleArgument() {
649649
doc.ellipse(0, 0, 0, 0, null);
650650
doc.circle(0, 0, 0, null);
651651
}
652+
653+
function test_addImageWithRGBAData() {
654+
const doc = new jsPDF();
655+
const rgbaData = new Uint8ClampedArray(16);
656+
const imageData = {
657+
data: rgbaData,
658+
width: 2,
659+
height: 2
660+
};
661+
662+
doc.addImage({
663+
imageData: imageData,
664+
x: 0,
665+
y: 0,
666+
width: 100,
667+
height: 100,
668+
compression: "FAST"
669+
});
670+
}

0 commit comments

Comments
 (0)