jsPDF not scaling html2canvas image correctly - javascript

I've got a function that takes a div, and saves it to a PDF document using jsPDF.
What I'm struggling with is why jsPDF isn't respecting my options
I want the PDF file to be landscape, 297mm wide (A4 landscape width) and then, then set the height of the PDF to the scaled size, and fill the PDF with the image.
My function is as below:
const input = document.getElementById(id);
const inputHeightMm = pxToMm(input.offsetHeight);
const inputWidthMm = pxToMm(input.offsetWidth);
const a4WidthMm = 297;
const a4HeightMm = 210;
const ratio = inputHeightMm/inputWidthMm;
console.log(a4WidthMm, a4WidthMm * ratio )
html2canvas(input)
.then((canvas) => {
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'landscape',
unit: "mm",
format: [a4WidthMm, a4WidthMm * ratio ]
});
pdf.addImage(imgData, 'PNG', 0, 0, a4WidthMm, a4WidthMm* ratio );
return pdf.save(`${v}.pdf`);
});
What happens though is that I get a PDF whereby the image is cut off, as it doesn't appear to scale it to the right size.
When checking the PDF it outputs, it also says its only 4.13" x 1.96" which is 104mm wide, not the 297mm passed to it

Can you try
pdf.addImage(imgData, 'PNG', 0, 0, undefined, undefined );
instead of
pdf.addImage(imgData, 'PNG', 0, 0, a4WidthMm, a4WidthMm* ratio );
and see if produces the same result?
the documentation points that addImage(imageData, format, x, y, width, height, alias, compression, rotation), width and height is used for resizing the image generated.
If you leave it undefined the image will be placed on the PDF in it's original size and ratio.

Related

Cannot export Canvas to PDF

I'm trying to print a canvas in a PDF file that I'm creating. The issue is that I always get a black rectangle in my PDF instead of the graph that I can see on my web page.
I generate the canvas like this:
<div class="col-md-6 featurePanel" style="display: none;">
<div id="canvasContainer_#featureID"></div>
</div>
It's filled by JS:
function SetFeatureValues(measID, measIndex, measCount, featureID, canvasID, name, humL, humR, femL, femR, thor, lumb, reference, type, color, ResTable) {
if (ResTable.length == measCount) {
var element = document.createElement("canvas");
element.id = canvasID;
element.classList.add("ThreeDDogGraph");
document.getElementById('canvasContainer_' + featureID).appendChild(element);
_3ddogvis.scene(0.0, true, ResTable, canvasID);
}
}
Then I export it to a PDF file:
function RenderFeature(pdf, currentTopPosition, currentLeftPasition, canvas) {
var newCanvas = document.createElement('canvas');
let context = newCanvas.getContext('2d');
newCanvas.width = width;
newCanvas.height = height;
context.drawImage(document.querySelector('#' + canvas.id), 0, 0, width, height);
var newCanvasImg = newCanvas.toDataURL("image/png");
pdf.addImage(newCanvasImg, 'png', currentLeftPasition, currentTopPosition, graphWidth, graphHeight, undefined, 'FAST');
}
After downloading the file I get a black rectangle in my PDF with the dimensions of my canvas.
EDIT:
If I try to copy the canvas (as propose obscure) I also get a black picture and the error message:
Unable to clone WebGL context as it has preserveDrawingBuffer=false
As I use a library done by someone else, I cannot set the preserveDrawingBuffer to true.
Is there a solution to get a picture from a canvas with preserveDrawingBuffer set to false?

Using drawImage with a video as a source appears to only copy one pixel?

I have a video that's streaming parts of a video into a MediaSource and playing it. I have a canvas that I want to draw the video on an then overlay some effects. However, in my handler, the call to drawImage, while it works, it only appears to copy a single pixel. The video itself plays fine.
I have a screenshot from my app, which has the source video next to the destination canvas element:
As you can sorta see, the solid color of the canvas is the color of the very first pixel of the source video.
The actual code in question looks similar to this:
startWatch() {
this.timer = setInterval(this.copyFrame.bind(this), 1000 / this.fps)
}
copyFrame() {
const { width, height } = this.video.getBoundingClientRect()
// I've tried the 3, 5, and 9 argument versions of this function
// width is 300 and height is 168.75.
this.ctx.drawImage(this.video, 0, 0, width, height, 0, 0, width, height)
}
(where this.video is a video element)
This code does "work" in that the canvas's color does update over time as the video plays. But it's always just the initial pixel. So the timing loop works, but the drawImage call isn't doing what I expect.
(1) Using const { width, height } may not be correct. Check the values you are given.
What happens if you console.log the width or height values, are they correct/expected numbers?
(2) "...But the drawImage call isn't doing what I expect."
ctx.drawImage( someElement, 0, 0, A1, A2, 0, 0, B1, B2 );
Consider the above code,
The first width/height (A1/A2) is for how much of the input to draw (it must match size of input's own width/height, otherwise cropping or margins will occur). Eg: A1 == 720 if input video is 720 pixels wide.
The second width/height (B1/B2) is for how much resize of the output to draw. Usually you want to fill entire canvas area so use canvas dimensions (aspect ratio is left to coder to implement as preferred).
In your shown code, your const { width, height } is either
Giving too small a number for A1/A2 params (it samples one pixel)
Giving too large a number for B1/B2 params (it stretches output so large that one pixel fills the canvas).
Possible solutions:
Try (if you must use bounding rectangle):
function copyFrame()
{
//# width is 300 and height is 168.75.
//const { width, height } = this.video.getBoundingClientRect()
let myRect = this.video.getBoundingClientRect();
this.ctx.drawImage( this.video, 0, 0,
this.video.videoWidth, this.video.videoHeight,
0, 0, myRect.width, myRect.height);
}
Alternativey you could just draw the picture into canvas size:
function copyFrame()
{
//# assuming your Canvas is called "this.cnvs"
this.ctx.drawImage( this.video, 0, 0,
this.video.videoWidth, this.video.videoHeight,
0, 0, this.cnvs.width, this.cnvs.height);
}
For aspect ratio you can use the below formula (swap all width & height references for a new drawn_width)
myRatio = ( vid_height / vid_width );
drawn_height = ( myCanvas_width * myRatio );

Pixel manipulation with FabricJS

I'm using FabricJS v1.5.0 and have a problem when manipulated pixel by pixel on the canvas.
this.canvas = new this.fabric.Canvas('canvas');
let imageData = this.canvas.contextContainer.getImageData(0, 0, this.canvas.getWidth(), this.canvas.getHeight());
//here a changed imageData
this.canvas.contextContainer.putImageData(imageData, 0, 0);
//data seted in canvas.
let imageAdd = new this.fabric.Image(image, {
left: 100,
top: 100,
width: 100,
height: 100
});
this.canvas.add(imageAdd).setActiveObject(imageAdd);
//in this moment, the canvas don't have more the imageData setted
Why add image clear imageData? I need to transform imageData in FabricJS Object to add in canvas?
Yes, I think you do need to turn your image data into a fabricjs image. When you need to save the state of the canvas you can do this:
var dataUrl;
....
// Save canvas state
dataUrl = canvas.toDataURL()
// Make changes to the canvas
Then when you need to restore the canvas from it's saved state:
var img = fabric.Image.fromURL(dataUrl, function (i) {
canvas.clear();
canvas.add(i);
canvas.renderAll();
});
You should then be able to add other images as before.
What you need to find out is if your image is actually an image object, because fabric.Image() needs your image object.
If it's a URL or Blob format of your image ( which I guess it is ), you need to use fabric.Image.fromURL() and provide the src of the image not the whole image object.
Here is an example on how to create an instance of fabric.Image from a URL string
fabric.Image.fromURL(link, function(img) {
img.set({
id : 'someId',
width : canvas.width / 2,
height : canvas.height / 2
});
canvas.add(img).renderAll().setActiveObject(img);
});

convert web page to a pdf with styles

I want to convert a web page to PDF including all the styles I've added.
I've used jspdf.js and html2Canvas. But I got only a blur picture in PDF form.
I've searched for many JavaScript codes but didn't find a correct one.
Script I have written is:
function getPDF() {
html2canvas(document.body, {
onrendered: function(canvas) {
var img = canvas.toDataURL("Image/jpeg");
//window.open(img);
var doc = new jsPDF({
unit: 'px',
format: 'a4'
});
doc.addImage(img, 'JPEG', 0, 0, 440, 627);
doc.save("download");
}
});
}
Thank You!
There may be a few things that are contributing.
Your encoder options for the canvas.toDataURL are missing, so you're getting the defaults. The defaults may be creating a low quality image. Try this for the highest quality JPEG image:
var img = canvas.toDataURL("Image/jpeg", 1.0);
You might also try creating your jsPDF object with measurements, rather than pixels:
var pdf = new jsPDF("p", "mm", "a4"); // jsPDF(orientation, units, format)
And when you add the image, scale it to the dimensions of the page:
pdf.addImage(imgData, 'JPEG', 10, 10, 190, 277); // 190x277 mm # (10,10)mm
See if that gives you a better image quality.

fabric.js: Does resizing image using Image.set() reduce image quality?

I need to reduce the height/width of an image before adding to a Canvas. We are using fabric.js.
When the image is resized using the image.set() function shown below, does it degrade the image quality?
imgAdd.onload = function () {
var img = new fabric.Image(imgAdd);
var imgSizeObj = img.getOriginalSize();
var newImgSize = calculateAspectRatioFit(imgSizeObj.width, imgSizeObj.height, canvas.width, canvas.height);
if (imgSizeObj.width > canvas.width || imgSizeObj.height > canvas.height) {
//Does img.set() preserve image resolution?
img.set({
width: newImgSize.width,
height: newImgSize.height
});
}
canvas.add(img);
}
The goal is to maintain image quality but automatically scale a large image to fit within the canvas.
Thank you,
No it doesnt.
The image is stored in a property of the object._element and does not change unless you apply some filter over it.
Even in that situation a copy is stored in object._originalElement.
Please try the following:
var scale = bef;
img.set({
scaleX: scale,
scaleY: scale
});
You can find more information on this here: https://github.com/kangax/fabric.js/issues/176

Categories

Resources