Html2Canvas Screenshot clarity - javascript

Using html2canvas for taking screenshots. And that screenshot converted to image and attach it with email.
These screenshot includes highcharts. Some time x axis and y axis displayed with some shadow effect. How can I avoid it?
var tempcanvas=document.createElement('canvas');
tempcanvas.width=2000;
tempcanvas.height=980;
var context=tempcanvas.getContext('2d');
context.imageSmoothingQuality = "High";
context.drawImage(canvas,0,0,1000,750);
var link=tempcanvas.toDataURL('image/png',1);
$scope.alert.message = link;
var blobBin = atob(link.split(',')[1]);
var array = [];
for(var i = 0; i < blobBin.length; i++) {
array.push(blobBin.charCodeAt(i));
}
$scope.file=new Blob([new Uint8Array(array)], {type: 'image/png'});

May this helps you something
All that is necessary is to set the dpi or scale options when you use html2canvas, and the resulting canvas should have your chosen dpi/scale.
function myRenderFunction(canvas) {
destination.appendChild(canvas);
}
// Get source element and destination div.
var element = document.getElementById('element');
var destination = document.getElementById('destination');
// Normal html2canvas rendering.
html2canvas(element, {
onrendered: myRenderFunction
});
// With dpi: 144 (scale: 1.5).
html2canvas(element, {
dpi: 144,
onrendered: myRenderFunction
});
// With scale: 2 (dpi: 192).
html2canvas(element, {
scale: 2,
onrendered: myRenderFunction
});

let me try to assess my comments and also suggest some code:
When I ran into this issue what solved for me is when I started playing around the options of html2canvas. Here you can find the available options. When you simply want to call the function you use html2canvas(element, options). Just as emran suggested in their answer, you can pass an object as the second argument to the html2canvas function which can contain canvas creation options. Try passing the following:
var options = {
async : false, // setting it to false may slow the generation a bit down
allowTaint : false, // I am not sure whether it helped or not but I remember setting it
imageTimeout : 100, // this further delays loading, however this solved a similar issue for me
foreignObjectRendering : true // it depends on the browser used whether it is allowed or not
}
Then you can call your html2canvas function similarly to what emran suggested, however, onrendered is no longer necessary since newer versions of html2canvas work with .then()
var element = document.getElementById('element');
var destination = document.getElementById('destination');
html2canvas(element, options).then(function(canvas) {
destination.appendChild(canvas);
});
Please note that there are several other interesting options at that page that might be useful for you. I can also suggest this article on the topic, this explanation of the options and this library that combines html2canvas and jsPDF for a direct HTML to PDF conversion.

Related

Creating an object copies old

I have multiple canvas setup in my HTML page. I access them via their respective ID in JS/jQuery.
In this canvas there is a "player"-character (a basic square), that has a constant size and a variable position.
I create the player object like this:
<script>
var player = {
xpos = 50,
ypos = 50
}
</script>
in the same <script></script> I have a function that looks like this:
async animate(cid2){
var c = Object.create(player);
cid = $(cid2)[0];
ctx = cid.getContext("2d");
c.xpos = 50;
c.ypos = 50;
while(c.xpos < 300){
await sleep(1100);
c.xpos = c.xpos + 50;
//draw a rectangle..
}
Once any <canvas id="test"> is clicked, (with a jQuery .click Function), animate is executed with the respective canvas id.
All of this works great, as long as there is one canvas on the page.
If I have say two canvas, the following happens in the console:
canvas1 is clicked!
X-Position: 50
X-Position: 100
canvas2 is clicked!
X-Position: 150
X-Position: 200
Although the second canvas is clicked and a Object.create should create a new object, it doesn't work: It still accesses the old object.
I am almost certain there is an answer to this on SO, but despite my best efforts, I can't find it, because I don't know what is actually going wrong.
Can anyone help me or refer me to a question I could ask?
Thank you in advance.
You could use the (I think ECMAscript 6) spread operator
const obj = { myProp: 'hi' };
const newObj = { ...obj };
newObj.myProp = 'ciao';
console.log(obj); // still { myProp: 'hi' }
Know you should have two separate objects instead of a copy.

Three.js toShapes() moved to new class in v81, how to access it

I've updated Three.js from v73 to v81.
I'm getting this error:
Uncaught TypeError: path.toShapes is not a function
On looking at the release documents, I found:
Changes to Path:
got rid of .actions (yay)
getPoints() moved to CurvePath
toShapes() moved to new class ShapePath
My code segment is this:
var shapes = [];
for (var i = 0; i < paths.length; ++i) {
// Turn each SVG path into a three.js shape
var path = d3.transformSVGPath( paths[i] );
// We may have had the winding order backward.
**var newShapes = path.toShapes(effectController.reverseWO);**
// Add these three.js shapes to an array.
shapes = shapes.concat(newShapes);
}
I could not find THREE.Path anywhere so I imported Path.js in case it would help, but to no avail. I'm new to Three.js so do not know if it's a noob question, but I've been at it for a day now and cannot figure it out.
The ShapePath class contains the method you are looking for: .toShapes(). The example the documentation links to also utilizes this method:
path = $d3g.transformSVGPath( thePaths[i] );
...
simpleShapes = path.toShapes(true);
The issue is that the d3-threeD source that includes the .transformSVGPath() method still defines var path = new THREE.Shape();.
Notice in the example, that there is a snippet of code that is taken/derived from d3-threeD, but is updated to use THREE.ShapePath():
...
function transformSVGPath(pathStr) {
var path = new THREE.ShapePath();
...
I would recommend you follow the example source and include the updated .transformSVGPath() method which uses THREE.ShapePath().

Update fabric.js Path points dynamically

I'm trying to add points to a path object dynamically. When I do, the path renders correctly, but the bounding rectangle never gets updated, making it nearly impossible for a user to select and move the path on canvas.
As you can see in the code below, the path is initially created with a single point, then I dynamically add a second point as well as a control point. After doing this, the bounding rectangle never updates:
var canvas = new fabric.Canvas('c');
canvas.backgroundColor = '#f5f5f5';
var path = new fabric.Path('M 0 20',{
left: 100,
top: 100,
stroke: 'black',
fill: ''
});
canvas.add(path);
var commandArray = [];
commandArray[0] = 'Q';
commandArray[1] = 50;
commandArray[2] = 100;
commandArray[3] = 100;
commandArray[4] = 20;
path.path[1] = commandArray;
canvas.renderAll();
I also tried calling path.setCoords(), but that did not make any difference. How can I get the bounding rectangle to update its dimensions after adding points to a path?
Here's a fiddle: http://jsfiddle.net/flyingL123/17ueLva2/2/
In fabric 3.3.2 I solved it combining the answers above:
var dims = path._calcDimensions()
path.set({
width: dims.width,
height: dims.height,
left: dims.left,
top: dims.top,
pathOffset: {
x: dims.width / 2 + dims.left,
y: dims.height / 2 + dims.top
},
dirty: true
})
path.setCoords()
This correctly updates my path bounding box after adding points like:
path.set({path: points})
I am not sure though, if this works with negative top and left values, but I didn't need that in my case. I guess the main thing is that the _parseDimensions() method was renamed to _calcDimensions().
Please, fabricjs does not support adding point dinamically as of now.
To make it work you can add points like you are doing and then use internal method path._parseDimensions() each time you add points and desire to update bounding box dimension.
var dims = path._parseDimensions();
path.setWidth(dims.width);
path.setHeight(dims.height);
path.pathOffset.x = path.width/2;
path.pathOffset.y = path.height/2;
path.setCoords();
Look this updated fiddle that has the necessary code to solve your problem.
I hope it works for every situation.
http://jsfiddle.net/17ueLva2/6/
It ended up being more complicated. If a point is added to the path that results in _parseDimensions returning a left value that is negative, the path would jump around the screen. For my use case, I need the path to stay in place while points are added and manipulated. This fiddle shows my working solution:
http://jsfiddle.net/flyingL123/8763bx2q/8/
If you run it with a JS console open, you will see the script pausing after each additional point is added, or current point is manipulated. As this happens you will see that the path does not get moved along the canvas, which is the desired behavior. After all the break points complete, you will see that the curve is centered within its selection box.
If there is an easier way to achieve this behavior, I would love to know.
Here's the function I'm using to set the dimensions just in case the fiddle link ever goes away:
function updateDims() {
var dims = path._parseDimensions(),
prevDims = path.prevDims || {},
leftDiff = dims.left - (prevDims.left || 0),
topDiff = dims.top - (prevDims.top || 0);
path.setWidth(dims.width);
path.setHeight(dims.height);
if (dims.left < 0) {
path.pathOffset.x = path.width/2 + dims.left;
path.left = path.left + leftDiff;
} else {
path.pathOffset.x = path.width/2;
}
if (dims.top < 0) {
path.pathOffset.y = path.height/2 + dims.top;
path.top = path.top + topDiff;
} else {
path.pathOffset.y = path.height/2;
}
path.prevDims = dims;
path.setCoords();
}
I couldn't find any new way to do this. But I figured out something like below;
create an SVG path string with modifications.
create a new fabric path object.
replace path, width, height and pathOffset properties of the original path object with the properties of the new path object.
setCoords. renderAll etc...
It may not be much efficient. But it was the solution for me.
var pathObject = new fabric.Path("M0,0 L100,100 ~ Z");
var updatedPath = new fabric.Path("M50,100 L120,46 ~ Z");
pathObject.set({
path : updatedPath.path,
width : updatedPath.width,
height : updatedPath.height,
pathOffset: updatedPath.pathOffset
});
pathObject.setCoords();
On my setup, it says path._parseDimensions is not a function.
I didn't try to solve it. I have to change all path content. So my solution seems better for me :)
Fabric 4.6.0
If you look at constructor of Path, it calls
fabric.Polyline.prototype._setPositionDimensions.call(this, options);
in the end. Where this is your Path and options is your Path constructor second argument. It is enough to call the method above on each path addition/removal to update the position and bounds.

draw preloaded image into canvas

Once again, completely out of my depth but I need to preload some images and then add them to the page when 'all elements (including xml files etc.)' are loaded. The images and references are stored in an array for later access. Trying to draw and image from that array throws an error yet I know it is available as I can just appendTo the page:
preloadImages: function (loadList, callback) {
var img;
var loadedFiles = [];
var remaining = loadList.length;
$(loadList).each(function(index, address ) {
img = new Image();
img.onload = function() {
--remaining;
if (remaining <= 0) {
callback(loadedFiles);
}
};
img.src = loadList[index];
loadedFiles.push({file: 'name of image to be loaded', image: img }); //Store the image name for later refernce and the image
});
}
//WHEN CERTAIN OTHER CONDITIONS EXIST I CALL THE FUNCTION BELOW
buildScreen: function ( imageLocs, image){
//THIS FUNCTION LOOPS THROUGH imageLocs (XML) AND CREATES CANVAS ELEMENTS, ADDING CLASSES ETC AND DRAWS PART OF A SPRITE (image)
//INTO THE CANVASES CREATED
var ctx = $('ID of CANVAS').get(0).getContext("2d");
var x = 'position x in imageLocs'
var y = 'position y in imageLocs'
var w = 'width in imageLocs'
var h = 'position x in imageLocs'
ctx.drawImage(image, x,y, w, h, 0, 0, w, h); //THIS THROWS AN ERROR 'TypeError: Value could not be converted to any of: HTMLImageElement, HTMLCanvasElement, HTMLVideoElement'
//$(image).appendTo("#innerWrapper") //YET I KNOW THAT IT IS AVAILABE AS THIS LINE ADDS THE IMAGE TO THE PAGE
}
Problem
The issue is caused because you are passing a jQuery object to a native function, in this case ctx.drawImage, drawImage will only support native objects.
startSequence : function(){
$('#innerWrapper').empty();
var screenImageRef = $.grep(ST.imageFilesLoaded, function(e){
return e.file == 'AtlasSheet'
});
var screenImage = $(screenImageRef[0].image);
var imageLocsRef = $.grep(ST.xmlFilesLoaded, function(e){
return e.file == 'IMAGELOCS'
});
var imageLocs = $(imageLocsRef[0].xml);
//$(screenImage).appendTo("#innerWrapper") //appends screenImage
Utilis.buildScreen('1', imageLocs, screenImage, ST.didYouSeeIt, 'ST')
}
Your screenImage var is created by $(screenImageRef[0].image), this will return a jQuery object that wrappers the native image object. To get back to the original native image object use the following:
screenImage.get(0)
or
screenImage[0]
The former is the jQuery supported way.
Solution
So the fix to your code should be either, changing the following line:
Utilis.buildScreen('1', imageLocs, screenImage.get(0), ST.didYouSeeIt, 'ST');
Or changing the line in the buildScreen method:
ctx.drawImage(image.get(0), x,y, w, h, 0, 0, w, h);
... Whichever you prefer.
Confusion when debugging
The reason why everything appears to work when you append the image, is because you are using jQuery to append the image, and jQuery supports being passed jQuery wrapped elements. If you had tried to append your screenImage using native functions i.e. Element.appendChild() you would have got similar errors.
Just to help in future, it's always best to use console.log to find out what type/structure a variable actually has. Using console.log on your previous image var would have given a strange object dump of the jQuery wrapper (which might have rang alarm bells), rather than the expected [object HTMLImageElement] or some other image/console related output (depending on the browser).
I think your image preloader isn't quite correct as it uses the same img variable for all images.
Here is one that I know works well: https://gist.github.com/eikes/3925183

Image appearing multiple times on canvas?

I'm drawing a simple dynamic canvas and I'm wondering how I can make the .png file in my drawImage method appear like 40 times at different places on my canvas at the same time?
Thanks beforehand! :)
Thank you all very much for your reply! This is as far as I've gotten now:
<script type="text/javascript">
var ctx;
var imgBg;
var imgDrops;
var x = 40;
var y = 0;
function setup() {
var canvas = document.getElementById('canvasRegn');
if (canvas.getContext) {
ctx = canvas.getContext('2d');
setInterval('draw();', 36);
imgBg = new Image();
imgBg.src = 'dimma.jpg';
imgDrops = new Image();
imgDrops.src = 'drop.png';
}
}
function draw() {
drawBackground();
for(var i=0; i <= 40; i++) {
ctx.drawImage (imgDrops, x, y);
y += 3;
if(y > 450)
y = -20;
x=Math.random()*600;
}
}
function drawBackground(){
ctx.drawImage(imgBg, 0, 0);
}
</script>
My problem is now that the images are jumping all over the place... I want them "falling" down slowly from above and coming back around:(
Have a look at this fiddle http://jsfiddle.net/joevallender/D83uC/10/
I made it to explain some basics of canvas to a friend recently. Although I'm using shapes instead of .png files I think the loop you are looking for is the same.
The key bit of code being this loop below
setInterval(function(){
// clear stage
context.clearRect(0, 0, width, height);
for(var i = 0; i < balls.length; i++) {
balls[i].move(balls);
}
}, 1000/FPS)
FPS is a variable, and .move() is a function that calculated new co-ordinates for and then re-draws the ball object.
I think it might simply not clearing the 'stage' context.clearRect(0, 0, width, height);
EDIT Perhaps that example had too much going on in it to be useful.
Please see a much earlier version http://jsfiddle.net/joevallender/D83uC/2 that simple animates the ball. The main point remains though, you need to clear the canvas if you don't want the 'trails'
Think of canvas like windows paint, not flash. The things you have drawn aren't editable objects. You need to redraw the whole thing each time. (Unless you use a JS library that makes things seem more like flash - but I'm guessing you want to learn without helper libraries at first)
As I said, I was explaining canvas to someone recently and you can see the various stages between the two links I've sent you by changing the number on the end of the URL http://jsfiddle.net/joevallender/D83uC/3
http://jsfiddle.net/joevallender/D83uC/4
etc.
EDIT2
Or if I've misunderstood, post a jsfiddle and let us know what is wrong with it
This is what you need: http://edumax.org.ro/extra/new/imagetut/
You can get the code here: http://edumax.org.ro/extra/new/imagetut/script.js
The relevant part is this:
function draw_image(){
//draw one image code
}
window.onload = function () {
for (var i=0;i<10;i++){
for (var j=0;j<10;j++){
draw_image(10+i*40, 10+j*40, 40, 40);
}
}
}
This code only explains the concept, it will not work by itself, for a full version check the link above.

Categories