dom to image library generate low quality image - javascript

I used dom-to-image to download div content as image but it gives me a low quality image which is not clear
my code :
<script>
var node = document.getElementById('my-certificate');
var btn = document.getElementById('download-btn');
var options = {
quality: 0.99
};
btn.onclick = function() {
domtoimage.toBlob(node, options)
.then(function(blob) {
window.saveAs(blob, 'my-certification.png');
});
}
</script>
any idea in how to generate better quality ?

Use width and height in the options to scale up the node before the image will be renderd. (If you do not, the quality is like a screen-shoot)
var options = {
quality: 0.99,
width: 2000,
height: 2000,
};

Try this.
var scale = 2;
domtoimage.toBlob(domNode, {
width: domNode.clientWidth * scale,
height: domNode.clientHeight * scale,
style: {
transform: 'scale('+scale+')',
transformOrigin: 'top left'
})
This worked for me.

Related

cropper js resize and crop with image magic

How to perform resize and crop with ImageMagick using the data from cropperjs?
The user can upload a large image and zoom/pan to crop. Tried using blob but it looses too much quality and times out too often.
Example from fiddle with the following data:
Original Width: 1280
Original Height: 720
Width: 424.8717011756327
Height: 238.9903319112934
X: -155.17118867901692
Y: -1.4989251522088705
Scale: 23.82
Tried with this but it crops the wrong area. Also tried scaling the original image but that's too big for the server to handle.
convert orignial.jpg -resize "1280x720^" -gravity center -crop 424x238+-155+-1 +repage result.jpg
Example:
https://jsfiddle.net/1knw3a5e/
JS code:
$(function() {
var image = $('#crop-image');
var zoomSlider = document.getElementById('zoom-slider');
var canvasSize = null;
var pictureContainer = $('.picture-frame');
var maxPictureContainerWidth = parseFloat(pictureContainer.css('max-width')) || 450;
var maxPictureContainerHeight = parseFloat(pictureContainer.css('max-height')) || 350;
var isSliderInUse = false;
// Wall is in Cm, convert to Inches to work out pixel sizes at 300dpi
var wallWpx = (0.393700787 * pictureContainer.attr('data-width')) * 300; // data-width is the wall width in pixels
var wallHpx = (0.393700787 * pictureContainer.attr('data-height')) * 300; // data-height is the wall height in pixels
var sampleImageScaleFactor = (image.attr('width') / image.attr('original-width'));
var wallSize = {
width: wallWpx * sampleImageScaleFactor, // scaling the wall size corresponding the sample size
height: wallHpx * sampleImageScaleFactor,
originalWidth: pictureContainer.attr('data-width'),
originalHeight: pictureContainer.attr('data-height')
};
var wallAspectRatio = wallSize.originalWidth/wallSize.originalHeight;
var pictureContainerSizes = {
'width': maxPictureContainerWidth * (wallAspectRatio > 1 ? 1 : wallAspectRatio) ,
'height': maxPictureContainerHeight / (wallAspectRatio > 1 ? wallAspectRatio : 1)
};
pictureContainer.css(pictureContainerSizes).removeClass('hidden');
var zoomStep = 0.2;
var biggerSide = null;
var zoomModal = $('#modal-warning');
var handleZoomHold, handleZoomFired;
image.cropper({
zoom: 0.2,
guides: false,
cropBoxResizable: false,
cropBoxMovable: false,
//viewMode: 3,
dragMode: 'move',
left: 0,
top: 0,
//width: canvasSize.width,
//height: canvasSize.height,
//aspectRatio: 1,
toggleDragModeOnDblclick: false,
zoomOnTouch: true,
zoomOnWheel: true
});
// Event
image.on('built.cropper', function() {
image.cropper('setCropBoxData', {
left: 0,
top: 0,
width: pictureContainerSizes.width,
height: pictureContainerSizes.height
});
canvasSize = {
width: image.cropper('getCropBoxData').width,
height: image.cropper('getCropBoxData').height
};
biggerSide = canvasSize.width === image.cropper('getImageData').width ? 'width' : 'height';
var savedCropperSettings = {
sliceW: parseFloat($('input[name=sliceW]').val()),
sliceH: parseFloat($('input[name=sliceH]').val()),
sliceX: parseFloat($('input[name=sliceX]').val()),
sliceY: parseFloat($('input[name=sliceY]').val()),
scale: parseFloat($('input[name=scale]').val()) // saved adoptedZoomFactor
};
if (!savedCropperSettings.scale) {
return;
}
/* restoring saved settings */
image.cropper('zoomTo', canvasSize[biggerSide]/(wallSize[biggerSide]/savedCropperSettings.scale.toFixed(1)));
var cropboxData = image.cropper('getCropBoxData');
var scaleFactor = wallSize.originalHeight / cropboxData.height;
image.cropper('setCanvasData', {
left: savedCropperSettings.sliceX / scaleFactor + cropboxData.left,
top: savedCropperSettings.sliceY / scaleFactor + cropboxData.top
});
});
var adoptedZoomFactor = NaN;
var adoptedZoomElement = $('#adoptedZoom');
image.on('crop.cropper', function() {
var data = image.cropper('getData');
var canvasData = image.cropper('getCanvasData');
var cropboxData = image.cropper('getCropBoxData');
var scaleFactor = wallSize.originalHeight / cropboxData.height;
adoptedZoomFactor = parseFloat((wallSize[biggerSide] / data[biggerSide]).toFixed(2));
adoptedZoomElement.text(adoptedZoomFactor);
$('input[name=sliceW]').val(canvasData.width * scaleFactor);
$('input[name=sliceH]').val(canvasData.height * scaleFactor);
$('input[name=sliceX]').val((canvasData.left - cropboxData.left) * scaleFactor);
$('input[name=sliceY]').val(canvasData.top * scaleFactor);
$('input[name=scale]').val(adoptedZoomFactor);
});
});
That cropper tool does not work correctly on my Mac in Safari or Firefox or Chrome. It does not respect the scale values that are entered. It always comes out with results of scale=1. Perhaps I am doing it wrong.
But if you want to do it in ImageMagick, the correct way would be:
Original:
Cropper Screen Snap:
Cropper Result (dimensions 320x180; still scale=1):
Using ImageMagick (dimensions 640x360):
ww=320
hh=180
xx=40
yy=60
rotate=0
scale=2
scale=`convert xc: -format "%[fx:$scale*100]" info:`
convert barn.jpg -virtual-pixel white -define distort:viewport=${ww}x${hh}+${xx}+${yy} -filter point -distort SRT "$rotate" +repage -resize $scale% test.jpg
Note that ImageMagick -distort SRT permits scaling, but the scale is done before the cropping from the viewport. So I had to use the viewport crop first and then add -resize in percent (as scale=2 --> scale=200%)
The reason I used -distort SRT with the viewport crop is that it would allow offset cropping when the xx and yy values are negative. You cannot do that with a simple -crop.
So for example:
ww=320
hh=180
xx=-40
yy=-60
rotate=0
scale=1
scale=`convert xc: -format "%[fx:$scale*100]" info:`
convert barn.jpg -virtual-pixel white -define distort:viewport=${ww}x${hh}+${xx}+${yy} -filter point -distort SRT "$rotate" +repage -resize $scale% test2.jpg
If you download the image, you will see it is padded at the top and right with white, but still has a size of 320x180.
If you are cropping only within the bounds of the image, then you can use -crop and the Imagemagick command would be:
ww=320
hh=180
xx=40
yy=60
rotate=0
scale=2
scale=`convert xc: -format "%[fx:$scale*100]" info:`
convert barn.jpg -crop ${ww}x${hh}+${xx}+${yy} +repage -resize $scale% test4.jpg
Which produces the same results as my original viewport crop.
I've had success just using the data from getData without doing any math on the results.
var croppable = $('.croppable');
croppable.cropper({
autoCrop: true,
viewMode: 0,
background: false,
modal: true,
zoomable: true,
responsive: false,
crop: function(e) {
data = croppable.cropper('getData');
$('#extract_image_crop_x').val(data.x);
$('#extract_image_crop_y').val(data.y);
$('#extract_image_crop_width').val(data.width);
$('#extract_image_crop_height').val(data.height);
$('#extract_image_crop_rotate').val(data.rotate);
}
});

FabricJS v.2 and AngularJS – set canvas background image dynamically

I am using this function to define my fabricJS canvas background image:
$scope.bgImage = function () {
const {
ipcRenderer
} = require('electron')
ipcRenderer.send('openFile', () => {
})
ipcRenderer.once('fileData', (event, filepath) => {
fabric.Image.fromURL(filepath, function (image) {
var hCent = canvas.getHeight / 2;
var wCent = canvas.getWidth / 2;
canvas.setBackgroundColor({
source: filepath,
top: hCent,
left: wCent,
originX: 'center',
originY: 'middle',
repeat: 'no-repeat',
scaleX: 10,
scaleY: 10
}, canvas.renderAll.bind(canvas));
})
})
};
All the parameters shoulkd be change on the fly with some html inputs. Setting the repeat is working fine and i can change it dynamically.
But I am simply not able to change the scale of my background image. Using the above function I would expect that my background image would be:
would be not repeated (ok. is working)
positioned in the middle/center of my canvas (it is not)
would be scaled by a factor of 10 (it is not)
I am making again a newbish mistake? Or ist this realted to some changes in image handling in FabricJS v.2
canvas.setBackgroundImage() Use this function to add Image as a background.
To get Height/Width of canvas use canvas.getHeight()/canvas.getWidth(),these are functions. Or else you can use the properties > canvas.height/canvas.width respectively.
DEMO
// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');
var hCent = canvas.getHeight() / 2;
var wCent = canvas.getWidth() / 2;
canvas.setBackgroundImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
originX: 'center',
originY: 'center',
left: wCent,
top: hCent,
scaleX: 1,
scaleY: 1
});
fabric.util.addListener(document.getElementById('toggle-scaleX'), 'click', function () {
if (!canvas.backgroundImage) return;
canvas.backgroundImage.scaleX = canvas.backgroundImage.scaleX < 1 ? 1 : fabric.util.toFixed(Math.random(), 2);
canvas.renderAll();
});
fabric.util.addListener(document.getElementById('toggle-scaleY'), 'click', function () {
if (!canvas.backgroundImage) return;
canvas.backgroundImage.scaleY = canvas.backgroundImage.scaleY < 1 ? 1 : fabric.util.toFixed(Math.random(), 2);
canvas.renderAll();
});
canvas {
border: 1px solid #999;
}
button {
margin-top: 20px;
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
<button id="toggle-scaleX">Toggle scaleX</button>
<button id="toggle-scaleY">Toggle scaleY</button>

SVG Path Line Animation in IE

I have some path animations on a page and they work fine in all browsers except IE10/11. However I have some much more simpler animations doing the same thing on other pages, just with fewer of them, using pretty much the same code and they seem okay.
I think it may well be a performance bottleneck or so associated with IE.
If you view http://codepen.io/jhealey5/pen/YXzbYY in IE10/11 you'll see there is quite a noticeable problem where the svgs appear glitchy or not fully rendered. Can't quite figure out what it is.
The relevant JS code from codepen:
var cfg = {
easing: [0.165, 0.84, 0.44, 1],
duration: 1200,
delay: 500,
layerDelay: 7000,
width: 28,
positioning: true,
colors: [
'#027CA5',
'#75B5C6',
'#00FFD0',
'#00B994',
'#BEF5FE'
]
}
$('.shape-layer').each(function(i) {
var $this = $(this);
setTimeout(function() {
var $paths = $this.find('path');
strokeSetup($paths);
strokeOut($paths);
}, cfg.layerDelay * i);
});
function strokeSetup($el) {
$el.each(function(i) {
var $this = $(this),
pLen = Math.ceil($this.get(0).getTotalLength());
$this.css({
'stroke-dasharray': pLen,
'stroke-dashoffset': pLen,
'stroke-width': cfg.width
});
});
}
function strokeOut($el) {
var pathCount = $el.length,
iterationCount = pathCount;
$el.each(function(i) {
var $this = $(this),
pLen = Math.ceil($this.get(0).getTotalLength()),
color = cfg.colors[getRandom(0, cfg.colors.length)];
setTimeout(function() {
$this.css({
'stroke': color
});
if (cfg.positioning) {
var side = ['top', 'bottom', 'left', 'right'],
cssO = {};
$this.parent().css({
top: 'auto',
bottom: 'auto',
left: 'auto',
right: 'auto'
});
cssO[side[getRandom(0, 1)]] = getRandom(0, 40) + '%';
var firstPos = cssO[Object.keys(cssO)[0]],
sideAmount = (parseInt(firstPos) < 20) ? 100 : 20;
cssO[side[getRandom(2, 3)]] = getRandom(0, sideAmount) + '%';
$this.parent().css(cssO);
}
$this.velocity({
'stroke-dashoffset': 0,
}, {
duration: cfg.duration,
easing: cfg.easing
});
if (!--iterationCount) {
strokeIn($el);
}
}, cfg.delay * i);
});
}
function strokeIn($el) {
var pathCount = $el.length,
iterationCount = pathCount;
$el.each(function(i) {
var $this = $(this),
pLen = Math.ceil($this.get(0).getTotalLength());
setTimeout(function() {
$this.velocity({
'stroke-dashoffset': pLen
}, {
duration: cfg.duration,
easing: cfg.easing
});
if (!--iterationCount) {
strokeOut($el);
}
}, cfg.delay * i);
});
}
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
You may know this but All Internet Explorer versions do not support SMIL Animating paths/ strokes etc is SMIL. Even via JavaScript. And jQuery is not fully compatible with the SVG DOM. Despite this manipulation being via CSS SVG properties, SVG "animations" via CSS are not ideal.
SMIL is dying & will be depreciated, which is why I recommend spending more time with libs like Snap.svg (ie9+) or Raphaël (ie6+), personally I would lean more towards Snap.
Anyway what about this animation you made?
In your circumstance from a wise professional standpoint this is not a scenario for progressive enhancement. Meaning you should compensate by turning that SVG animation into either a video, gif or static image and using it as a failback for IE browsers. e.g. via modernizr or whatever.
It is completely wrong that everything has to look the same in each browser. I believe a static jpg image is sufficient for those who lack features in this scenario.
Another thing I'm always aware of is that In regards to SVG Internet Explorer has "broke" compatibility up the chain of IE versions.

Scale loaded SVG and zoom in with snap svg

So basically what I need to do is to load an SVG with SNAP.svg and add an effect (zoom in) I understand that in order to achieve this I need to respect this :
Load the SVG file
Scale de SVG in (0 ?)
Append this SVG
Add a transform effect (with the suitable scale)
The problem is that I need to display this in a 650 width and 280 height size.
The SVG I'm loading, witch I'll name it 'map' is in 1920 width and 1080 height.
This is my code so far :
<svg id="svg" width="650px" height="280px"></svg>
<script type="text/javascript">
var s = Snap("#svg");
var map = Snap.load("./src/map.svg", function (f) {
g = f.select("g");
var t = Snap.matrix().scale(0.35);
s.append(g);
g.group(g.selectAll("path")).transform(t);
});
</script>
It seems the scale instruction is working find but not the animation.
Also, how I can center this loaded SVG not matter what scale it takes ?
Thank you !
UPDATE :
I managed to add some effects but I don't think the way I'm doing it it's the correct one :
var carte = Snap.load("./src/carte.svg", function (f) {
g = f.select("g");
//var t = Snap.matrix().scale(0.35);
s.append(g);
//Set the map in first position
var firstScene = new Snap.Matrix();
firstScene.translate(300, 160);
firstScene.scale(0.05);
//Zoom effect
var secondScene = new Snap.Matrix();
secondScene.scale(2.0);
secondScene.translate(-850, -360);
//Move the matrix till desired point (not finish)
var threeScene = new Snap.Matrix();
threeScene.translate(-850, -360);
g.animate({ transform: firstScene }, 0, function() {g.animate ({ transform: secondScene}, 1500, mina.linear )});
});
It seems impossible to add a timer or more than two effects ?
Just as an alternative if there are quite a few sequenced animations I may be tempted to write a function to handle an array of animations. It could look something like this...
function nextFrame ( el, frameArray, whichFrame ) {
if( whichFrame >= frameArray.length ) { return }
el.animate( frameArray[ whichFrame ].animation,
frameArray[ whichFrame ].dur,
frameArray[ whichFrame ].easing,
nextFrame.bind( null, el, frameArray, whichFrame + 1 ) );
}
var block = s.rect(100, 100, 100, 100, 20, 20);
.attr({ fill: "rgb(236, 240, 241)", stroke: "#1f2c39",
strokeWidth: 3, transform: 's1' });
var frames = [
{ animation: { transform: 's0.4,0.4,200,200' }, dur: 1000, easing: mina.bounce },
{ animation: { transform: 't-100,-80' }, dur: 1000, easing: mina.bounce },
{ animation: { transform: 's1.2,1.2,300,300t200,-100' },dur: 1000, easing: mina.bounce }
];
nextFrame( block, frames, 0 );
jsfiddle
I think if you want to sequence animations, the most elegant way would be to use promises. For that all you would need is to wrap animate function in Q library or jQuery.Deferred. Here is the example I put together on jsFiddle https://jsfiddle.net/stsvilik/Lnhc77b2/
function animate(obj, conf, duration, asing) {
var def = Q.defer();
obj.animate(conf, dur, easing, function(){
def.resolve();
});
return def.promise;
}
This seems to work fine, but like lan said above, maybe his method it's better for doing animations
var carte = Snap.load("./src/carte.svg", function (f) {
g = f.select("g");
//var t = Snap.matrix().scale(0.35);
s.append(g);
//Load the map
var firstScene = new Snap.Matrix();
firstScene.translate(295, 160);
firstScene.scale(0.04);
//Set scene 1
var secondScene = new Snap.Matrix();
secondScene.scale(0.4);
secondScene.translate(-300, -10);
//Set scene 2
var threeScene = new Snap.Matrix();
//threeScene.scale(0.5);
threeScene.translate(-825, -380);
//Set scene 3
var fourScene = new Snap.Matrix();
fourScene.scale(21.0);
fourScene.translate(-1164, -526);
var anim1 = function() {
g.animate({ transform: firstScene}, 0, anim2);
}
var anim2 = function() {
g.animate({ transform: secondScene}, 1500, mina.easing, anim3);
}
var anim3 = function() {
g.animate({ transform: threeScene}, 1000, mina.linear, anim4);
}
var anim4 = function() {
g.animate({ transform: fourScene}, 2000, mina.easing);
}
anim1();
});
Is the fact of adding several matrix a performance killer ? Or this is the way it should be done ?

Resize image which uses Jcrop

I use the Jquery Jcrop for cropping my images. Now I'm implementing a slider for resizing the image. I want the cropping and resizing to happend on the same page.
I do it like this:
$(document).ready(function() {
var img = $('#cropbox')[0]; // Get my img elem
var orgwidth, orgheight;
$("<img/>") // Make in memory copy of image to avoid css issues
.attr("src", $(img).attr("src"))
.load(function() {
orgwidth = this.width; // Note: $(this).width() will not
orgheight = this.height; // work for in memory images.
});
$('#cropbox').Jcrop({
onSelect: updateCoords
});
$("#imageslider").slider({
value: 100,
max: 100,
min: 1,
slide: function(event, ui) {
//$('ul#grid li').css('font-size',ui.value+"px");
resizeImage(orgwidth, orgheight);
}
});
});
And my simple resizeImage function:
function resizeImage(orgwidth, orgheight) {
var value = $('#imageslider').slider('option', 'value');
var width = orgwidth * (value / 100);
var height = orgheight * (value / 100);
$('#cropbox').width(width);
$('#cropbox').height(height);
$('#tester').val("org: "+orgwidth+" now: "+width);
}
The problem is that, as soon I turn on Jcrop I can't resize the image. How can I use both these functions at the same time?
I ended up destroying the jCrop while resizing and putting it back on after resize. Thanks anyway. Code:
function resizeImage(orgwidth, orgheight) {
jcrop_api.destroy();
var value = $('#imageslider').slider('option', 'value');
var width = orgwidth * (value / 100);
var height = orgheight * (value / 100);
$('#cropbox').width(width);
$('#cropbox').height(height);
$('#rw').val(width);
$('#rh').val(height);
initJcrop();
}
I had the same task to accomplish: resize the image with a slider where jCrop is applied. There are some more elements you have to resize also that jCrop created, not only the image. I ended up patching the jCrop plugin and here is the patch for latest jCrop-0.9.10.
Patch your jCrop. If you don't know how to apply the patch, just put the resizeImage function to line 1578 of jCrop (unimified version ofcourse):
--- /home/dr0bz/Desktop/jquery.Jcrop.js
+++ /home/dr0bz/workspace/profile_tuning/js/lib/jquery.Jcrop.js
## -1573,6 +1573,15 ##
ui: {
holder: $div,
selection: $sel
+ },
+
+ resizeImage: function(width, height) {
+ boundx = width;
+ boundy = height;
+ $([$img2, $img, $div, $trk]).each(function(index, element)
+ {
+ element.width(width).height(height);
+ });
}
};
Get the jCrop API:
var jCropApi;
$('#photo').Jcrop({}, function()
{
jCropApi = this;
});
Calc new height and width. If your are doing it with a slider, let the slider say return the new width of the image and you calculate new height with aspect ratio of the image:
var aspectRatio = width / height;
// newWidth returned by slider
var newHeight = Math.round(width / aspectRatio);
jCropApi.resizeImage(newWidth, newHeight);
There are some other points to keep an eye on. After each resize your should look that crop area is still in the viewport of the image. If you need i could post the complete source how i've done it for me: jCrop + jquery ui slider to resize the image.
Regards
What you can also do is make use of the setImage function of Jcrop, when the slider changes, call the setImage with the Jcrop api and set new width and height values like this:
var jcrop_api;
$('#cropbox').Jcrop({
onSelect: updateCoords
}, function() {
jcrop_api = this;
});
$("#imageslider").slider({
value: 100,
max: 100,
min: 1,
slide: function(event, ui) {
var value = $('#imageslider').slider('option', 'value');
var width = orgwidth * (value / 100);
var height = orgheight * (value / 100);
jcrop_api.setImage($(img).attr("src"), function() {
this.setOptions({
boxWidth: width,
boxHeight: height
});
});
$('#tester').val("org: "+orgwidth+" now: "+width);
}
});
What I am not sure about this technique is if it is the best solution because everytime you call the setImage function, jcrop creates a new Image object.
If what you want is that the resizing should be proportional, I don't think you need the slider (since it seems to be incompatible with jCrop). You could use jCrop and in the onChange event, ensure the proportionality (that is, implement the resizeImage function, modified).
That's what I think.
As an extension to Hermann Bier answer, i have added the jquery animation.
The resizing looks way better when it's animated :)
Implemented in Jcrop version: jquery.Jcrop.js v0.9.12
Locate the code:
ui: {
holder: $div,
selection: $sel
}
in jquery.Jcrop.js around line 1573
and replace it with:
ui: {
holder: $div,
selection: $sel
},
resizeImage: function(width, height) {
animationsTid = 500;
boundx = width;
boundy = height;
$($img2).animate({
width: width,
height: height,
}, { duration: animationsTid, queue: false });
$($img).animate({
width: width,
height: height,
}, { duration: animationsTid, queue: false });
$($div).animate({
width: width,
height: height,
}, { duration: animationsTid, queue: false });
$($trk).animate({
width: width,
height: height,
}, { duration: animationsTid, queue: false });
/*
//Old way of resizing, but without animation
$([$img2, $img, $div, $trk]).each(function(index, element){
element.width(width).height(height);
});
*/
}
Call to the function will animate the resize.
Feel free to delete the code between /* */ - I just kept it as an reference
Happy Coding :)

Categories

Resources