I want to draw a serialized DOM element in the canvas. It works perfect on
Chrome, Firefox, Safari and Opera but i have problems on Edge (and IE11 (this not crucial)).
This is how I handle the canvas drawing:
this._$svgElement = this._props._svgContainer.find('svg');
this._initSVGWidth = this._$svgElement.width();
this._initSVGHeight = this._$svgElement.height();
var svgURL = new XMLSerializer().serializeToString(this._$svgElement[0]);
var svgString = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgURL);
var newImageWidth, newImageHeight;
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
this._img = new Image();
this._img.onload = function () {
if (bowser.msie) {
newImageHeight = (this._initSVGHeight / this._initSVGWidth) * this._canvas.width;
newImageWidth = this._canvas.width;
if (newImageHeight >= this._canvas.height) {
newImageHeight = this._canvas.height;
newImageWidth = (this._initSVGWidth / this._initSVGHeight) * this._canvas.height;
}
} else {
newImageHeight = this._img.height / this._img.width * this._canvas.width;
newImageWidth = this._canvas.width;
if (newImageHeight > this._canvas.height) {
newImageHeight = this._canvas.height;
newImageWidth = this._img.width / this._img.height * this._canvas.height;
}
}
//original image values (never changed)
this._imgData = {
x: (this._canvas.width / 2 - newImageWidth / 2),
y: (this._canvas.height / 2 - newImageHeight / 2),
w: newImageWidth,
h: newImageHeight
};
this._ctx.drawImage(this._img, this._imgData.x, this._imgData.y, newImageWidth, newImageHeight);
}.bind(this);
this._img.src = svgString;
The result is a strange offset on the microsoft browsers:
Edge browser (wrong)
For Example: Chrome Browser (how it should be)
This is the SVG Test Source: https://s.cdpn.io/3/kiwi.svg
I don't know what the issue is here. Because even if I try to draw it with drawImage(this._img, 0, 0) the resulting image is cut off.
I'm thankful for any suggestions because we are out of any ideas.
Related
In a web application I'm using JSPDF to convert the html to pdf. All works fine, except for the images. After a while, I noticed that it adds images that point to a local resource; instead, it does not add images that point to an online resource, and leaves in the place of the image an empty space, as if he expected it but could not load it.
For example: <img src="img/house.jpg"/> is correctly added.
<img src="https://myurl.com/house.jpg"/> is not correctly added; there is an empty space instead of the image.
How can I solve it? Maybe store the image temporarily in local? I tried using addImage() but it is very hard to use, not only because I change the scale factor of pdf, but primarily because the content of the pdf is dynamic, and I do not know what size the images will have or their exact position.
You need to make sure the image(s) is/are loaded before addIMage(). The following code is what I used to convert multiple images online to a PDF file. It will rotate the image(s) based on the orientation of the images/page and set proper margin. Note that this code is for image only, not the Html page with embedded image(s), but the concept of img.onload remains the same.
As for image rotation, if you see a blank page after rotation, it could simply be that the image is out of bounds. See this answer for details.
function exportPdf(urls) {
let pdf = new jsPDF('l', 'mm', 'a4');
const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();
const pageRatio = pageWidth / pageHeight;
for (let i = 0; i < urls.length; i++) {
let img = new Image();
img.src = urls[i];
img.onload = function () {
const imgWidth = this.width;
const imgHeight = this.height;
const imgRatio = imgWidth / imgHeight;
if (i > 0) { pdf.addPage(); }
pdf.setPage(i + 1);
if (imgRatio >= 1) {
const wc = imgWidth / pageWidth;
if (imgRatio >= pageRatio) {
pdf.addImage(img, 'JPEG', 0, (pageHeight - imgHeight / wc) / 2, pageWidth, imgHeight / wc, null, 'NONE');
}
else {
const pi = pageRatio / imgRatio;
pdf.addImage(img, 'JPEG', (pageWidth - pageWidth / pi) / 2, 0, pageWidth / pi, (imgHeight / pi) / wc, null, 'NONE');
}
}
else {
const wc = imgWidth / pageHeight;
if (1 / imgRatio > pageRatio) {
const ip = (1 / imgRatio) / pageRatio;
const margin = (pageHeight - ((imgHeight / ip) / wc)) / 4;
pdf.addImage(img, 'JPEG', (pageWidth - (imgHeight / ip) / wc) / 2, -(((imgHeight / ip) / wc) + margin), pageHeight / ip, (imgHeight / ip) / wc, null, 'NONE', -90);
}
else {
pdf.addImage(img, 'JPEG', (pageWidth - imgHeight / wc) / 2, -(imgHeight / wc), pageHeight, imgHeight / wc, null, 'NONE', -90);
}
}
if (i == urls.length - 1) {
pdf.save('Photo.pdf');
}
}
}
}
If this is a bit hard to follow, you can also use .addPage([imgWidth, imgHeight]), which is more straightforward. The downside of this method is that the first page is fixed by new jsPDF(). See this answer for details. You can use window.open(pdf.output('bloburl')) to debug.
I have a problem on my project.
I am developing a perspective mockup creating module for designers. Users upload images and i get them for placing in mockups with making some perspective calculations. Then users can download this image. I made all of this on clientside with js.
But there is a problem for images which are drawn on canvas with perspective calculations like this;
Sample img: http://oi62.tinypic.com/2h49dec.jpg
orginal image size: 6500 x 3592 and you can see spread edges on image...
I tried a few technics like ctx.imageSmoothingEnabled true etc.. But result was always same.
What can i do for solve this problem? What do you think about this?
edit
For more detail;
I get an image (Resolution free) from user then crop it for mockup ratio. For example in my sample image, user image was cropped for imac ratio 16:9 then making calculation with four dot of screen. By the way, my mockup image size is 6500 x 3592. so i made scale, transform etc this cropped image and put it in mockup on canvas. And then use blob to download this image to client...
Thanks.
Solved.
I use perspective.js for calculation on canvas. so I made some revisions on this js source.
If you wanna use or check source;
// Copyright 2010 futomi http://www.html5.jp/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
// perspective.js v0.0.2
// 2010-08-28
/* -------------------------------------------------------------------
* define objects (name space) for this library.
* ----------------------------------------------------------------- */
if (typeof html5jp == 'undefined') {
html5jp = new Object();
}
(function() {
html5jp.perspective = function(ctxd, image) {
// check the arguments
if (!ctxd || !ctxd.strokeStyle) {
return;
}
if (!image || !image.width || !image.height) {
return;
}
// prepare a <canvas> for the image
var cvso = document.createElement('canvas');
cvso.width = parseInt(image.width) * 2;
cvso.height = parseInt(image.height) * 2;
var ctxo = cvso.getContext('2d');
ctxo.drawImage(image, 0, 0, cvso.width, cvso.height);
// prepare a <canvas> for the transformed image
var cvst = document.createElement('canvas');
cvst.width = ctxd.canvas.width;
cvst.height = ctxd.canvas.height;
var ctxt = cvst.getContext('2d');
ctxt.imageSmoothingEnabled = true;
ctxt.mozImageSmoothingEnabled = true;
ctxt.webkitImageSmoothingEnabled = true;
ctxt.msImageSmoothingEnabled = true;
// parameters
this.p = {
ctxd: ctxd,
cvso: cvso,
ctxo: ctxo,
ctxt: ctxt
}
};
var proto = html5jp.perspective.prototype;
proto.draw = function(points) {
var d0x = points[0][0];
var d0y = points[0][1];
var d1x = points[1][0];
var d1y = points[1][1];
var d2x = points[2][0];
var d2y = points[2][1];
var d3x = points[3][0];
var d3y = points[3][1];
// compute the dimension of each side
var dims = [
Math.sqrt(Math.pow(d0x - d1x, 2) + Math.pow(d0y - d1y, 2)), // top side
Math.sqrt(Math.pow(d1x - d2x, 2) + Math.pow(d1y - d2y, 2)), // right side
Math.sqrt(Math.pow(d2x - d3x, 2) + Math.pow(d2y - d3y, 2)), // bottom side
Math.sqrt(Math.pow(d3x - d0x, 2) + Math.pow(d3y - d0y, 2)) // left side
];
//
var ow = this.p.cvso.width;
var oh = this.p.cvso.height;
// specify the index of which dimension is longest
var base_index = 0;
var max_scale_rate = 0;
var zero_num = 0;
for (var i = 0; i < 4; i++) {
var rate = 0;
if (i % 2) {
rate = dims[i] / ow;
} else {
rate = dims[i] / oh;
}
if (rate > max_scale_rate) {
base_index = i;
max_scale_rate = rate;
}
if (dims[i] == 0) {
zero_num++;
}
}
if (zero_num > 1) {
return;
}
//
var step = 0.10;
var cover_step = step * 250;
//
var ctxo = this.p.ctxo;
var ctxt = this.p.ctxt;
//*** ctxt.clearRect(0, 0, ctxt.canvas.width, ctxt.canvas.height);
if (base_index % 2 == 0) { // top or bottom side
var ctxl = this.create_canvas_context(ow, cover_step);
var cvsl = ctxl.canvas;
for (var y = 0; y < oh; y += step) {
var r = y / oh;
var sx = d0x + (d3x - d0x) * r;
var sy = d0y + (d3y - d0y) * r;
var ex = d1x + (d2x - d1x) * r;
var ey = d1y + (d2y - d1y) * r;
var ag = Math.atan((ey - sy) / (ex - sx));
var sc = Math.sqrt(Math.pow(ex - sx, 2) + Math.pow(ey - sy, 2)) / ow;
ctxl.setTransform(1, 0, 0, 1, 0, -y);
ctxl.drawImage(ctxo.canvas, 0, 0);
//
ctxt.translate(sx, sy);
ctxt.rotate(ag);
ctxt.scale(sc, sc);
ctxt.drawImage(cvsl, 0, 0);
//
ctxt.setTransform(1, 0, 0, 1, 0, 0);
}
} else if (base_index % 2 == 1) { // right or left side
var ctxl = this.create_canvas_context(cover_step, oh);
var cvsl = ctxl.canvas;
for (var x = 0; x < ow; x += step) {
var r = x / ow;
var sx = d0x + (d1x - d0x) * r;
var sy = d0y + (d1y - d0y) * r;
var ex = d3x + (d2x - d3x) * r;
var ey = d3y + (d2y - d3y) * r;
var ag = Math.atan((sx - ex) / (ey - sy));
var sc = Math.sqrt(Math.pow(ex - sx, 2) + Math.pow(ey - sy, 2)) / oh;
ctxl.setTransform(1, 0, 0, 1, -x, 0);
ctxl.drawImage(ctxo.canvas, 0, 0);
//
ctxt.translate(sx, sy);
ctxt.rotate(ag);
ctxt.scale(sc, sc);
ctxt.drawImage(cvsl, 0, 0);
//
ctxt.setTransform(1, 0, 0, 1, 0, 0);
}
}
// set a clipping path and draw the transformed image on the destination canvas.
this.p.ctxd.save();
this.set_clipping_path(this.p.ctxd, [
[d0x, d0y],
[d1x, d1y],
[d2x, d2y],
[d3x, d3y]
]);
this.p.ctxd.drawImage(ctxt.canvas, 0, 0);
this.p.ctxd.restore();
}
proto.create_canvas_context = function(w, h) {
var canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
var ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = true;
ctx.mozImageSmoothingEnabled = true;
ctx.webkitImageSmoothingEnabled = true;
ctx.msImageSmoothingEnabled = true;
return ctx;
};
proto.set_clipping_path = function(ctx, points) {
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (var i = 1; i < points.length; i++) {
ctx.lineTo(points[i][0], points[i][1]);
}
ctx.closePath();
ctx.clip();
};
})();
The problem is (most likely, but no code shows so..) that the image is actually too big.
The canvas typically uses bi-linear interpolation (2x2 samples) rather than bi-cubic (4x4 samples). That means if you scale it down a large percentage in one chunk the algorithm will skip some pixels that otherwise should have been sampled, resulting in a more pixelated look.
The solution do is to resize the image in steps, ie. 50% of itself repeatably until a suitable size is achieved. Then use perspective calculations on it. The exact destination size is something you need to find by trial and error, but a good starting point is to use the largest side of the resulting perspective image.
Here is one way to step-down rescale an image in steps.
I combined two JQuery libs Jcrop/Iviewer to be able at the same time to zoom and clip an image. code is working on chrome but still have some issues in Mozilla.
I believe that drawImage causes that error IndexSizeError: Index or size is negative or greater than the allowed amount:
$('#crop').Jcrop({
onChange: imgSelect,
onSelect: imgSelect
});
function imgSelect(selection) {
var ctx = canvas.getContext('2d');
var xZoom = $("#image_input").iviewer('info', 'coords', 'true').x;
var yZoom = $("#image_input").iviewer('info', 'coords', 'true').y;
var zoom = $("#image_input").iviewer('info', 'zoom', 'true');
var w_orig = (selection.w * 100) / zoom;
var h_orig = (selection.h * 100) / zoom;
canvas.width = w_orig;
canvas.height = h_orig;
ctx.drawImage(
image,
((selection.x - $("#image_input").iviewer('info', 'coords', 'false').x) * 100) / zoom,
((selection.y - $("#image_input").iviewer('info', 'coords', 'false').y) * 100) / zoom,
w_orig,
h_orig,
0,
0,
w_orig,
h_orig
);
}
<div id="image_input" class="viewer"></div> <!--layer 1 zoom-->
<div id="crop"></div><!-- layer 2 crop-->
the issue occurs because for the very first time when you will click on the crop area (or the image) the values for w_orig and h_orig will be 0 as no actual area as been selected.
What i did to bypass this issue is add a if condition to check these values and when you get them to be 0 you change the value manually. It wont affect the normal cropping as next time when change event is fired you won't get w_orig and h_orig to be zero and hence if condition will not be true.
function imgSelect(selection) {
var ctx = canvas.getContext('2d');
var xZoom = $("#image_input").iviewer('info', 'coords', 'true').x;
var yZoom = $("#image_input").iviewer('info', 'coords', 'true').y;
var zoom = $("#image_input").iviewer('info', 'zoom', 'true');
var w_orig = (selection.w * 100) / zoom;
var h_orig = (selection.h * 100) / zoom;
canvas.width = w_orig;
canvas.height = h_orig;
if (c.w === 0 && c.h === 0)
{
//set any arbitrary values
c.w = 300;
c.h = 300;
}
ctx.drawImage(
image,
((selection.x - $("#image_input").iviewer('info', 'coords', 'false').x) * 100) / zoom,
((selection.y - $("#image_input").iviewer('info', 'coords', 'false').y) * 100) / zoom,
w_orig,
h_orig,
0,
0,
w_orig,
h_orig
);
}
http://kazopark.com/3d/car/
This animation is using a spritesheet.
This animations works completely fine in Firefox and Chrome, as well as the latest versions of IE.
However, when it comes to Safari, it's just not working as you'd expect. The animation speed is incredibly slow, dropping to as little as 15 FPS.
Is there anyway of making something like this work the same across all the browsers, without purposely slowing the others down?
I suspect this is to do with the inadequate way that Safari handles large images?
It should be noted, I have reduced the filesize all the way down to 1.8mb for this, down from 4.3mb. It's still not playing properly.
Code for animation:
/********************************
* INIT ON LOAD *
* PUBLIC *
*********************************/
KMS.background = function(){
var singleton = {};
this.parent;
this.imgHolder;
this.newWidth;
this.newHeight;
function init(){
parent = document.createElement('div');
parent.className = "KMS_div";
parent.id = "KMS_div_Background";
var holder = document.getElementById("KMS_div_Holder");
holder.appendChild(parent);
imgHolder = new Image();
imgHolder.id = "KMS_img_SpriteSheet";
imgHolder.src = "img/CarRoad_low2.jpg";
parent.appendChild(imgHolder);
newWidth = window.innerWidth / 800;
newHeight = window.innerHeight / 600;
imgHolder.style.width = 512 + "%";
imgHolder.style.height = 1365.4 + "%";
}
function BeginBackground(){
init();
var i = 0;
setInterval(function(){
parent.style.left = -(100 * (i % 5)) + "%";
parent.style.top = -(100 * (parseInt(i / 5) % 14)) + "%";
i++;
if(i === 63) i = 0;
console.log(i);
}, 50);
console.log("BG begin");
};
singleton.BeginBackground = BeginBackground;
return singleton;
}();
I'm drawing text on Canvas, and am disappointed with the quality of antialiasing. As far as I've been able to determine, browsers don't do subpixel antialising of text on Canvas.
Is this accurate?
This is particularly noticeable on iPhone and Android, where the resulting text isn't as crisp as text rendered by other DOM elements.
Any suggestions for high quality text out put on Canvas?
Joubert
My answer came from this link, maybe it will help someone else.
http://www.html5rocks.com/en/tutorials/canvas/hidpi/
The important code is as follows.
// finally query the various pixel ratios
devicePixelRatio = window.devicePixelRatio || 1,
backingStoreRatio = context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1,
ratio = devicePixelRatio / backingStoreRatio;
// upscale the canvas if the two ratios don't match
if (devicePixelRatio !== backingStoreRatio) {
var oldWidth = canvas.width;
var oldHeight = canvas.height;
canvas.width = oldWidth * ratio;
canvas.height = oldHeight * ratio;
canvas.style.width = oldWidth + 'px';
canvas.style.height = oldHeight + 'px';
// now scale the context to counter
// the fact that we've manually scaled
// our canvas element
context.scale(ratio, ratio);
}
Try adding the following META tag to your page. This seems to fix anti-aliasing issues I've had on iPhone Safari:
<meta name="viewport" content="user-scalable=no, width=device-width,
initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5" />
I realise this is an old question, but I worked on this problem today and got it working nicely. I used Alix Axel's answer above and stripped down the code I found there (on the web.archive.org link) to the bare essentials.
I modified the solution a bit, using two canvases, one hidden canvas for the original text and a second canvas to actually show the anti-aliaised text.
Here's what I came up with... http://jsfiddle.net/X2cKa/
The code looks like this;
function alphaBlend(gamma, c1, c2, alpha) {
c1 = c1/255.0;
c2 = c2/255.0;
var c3 = Math.pow(
Math.pow(c1, gamma) * (1 - alpha)
+ Math.pow(c2, gamma) * alpha,
1/gamma
);
return Math.round(c3 * 255);
}
function process(textPixels, destPixels, fg, bg) {
var gamma = 2.2;
for (var y = 0; y < textPixels.height; y ++) {
var history = [255, 255, 255];
var pixel_number = y * textPixels.width;
var component = 0;
for (var x = 0; x < textPixels.width; x ++) {
var alpha = textPixels.data[(y * textPixels.width + x) * 4 + 1] / 255.0;
alpha = Math.pow(alpha, gamma);
history[component] = alpha;
alpha = (history[0] + history[1] + history[2]) / 3;
out = alphaBlend(gamma, bg[component], fg[component], alpha);
destPixels.data[pixel_number * 4 + component] = out;
/* advance to next component/pixel */
component ++;
if (component == 3) {
pixel_number ++;
component = 0;
}
}
}
}
function toColor(colorString) {
return [parseInt(colorString.substr(1, 2), 16),
parseInt(colorString.substr(3, 2), 16),
parseInt(colorString.substr(5, 2), 16)];
}
function renderOnce() {
var phrase = "Corporate GOVERNANCE"
var c1 = document.getElementById("c1"); //the hidden canvas
var c2 = document.getElementById("c2"); //the canvas
var textSize=40;
var font = textSize+"px Arial"
var fg = "#ff0000";
var bg = "#fff9e1";
var ctx1 = c1.getContext("2d");
var ctx2 = c2.getContext("2d");
ctx1.fillStyle = "rgb(255, 255, 255)";
ctx1.fillRect(0, 0, c1.width, c1.height);
ctx1.save();
ctx1.scale(3, 1);
ctx1.font = font;
ctx1.fillStyle = "rgb(255, 0, 0)";
ctx1.fillText(phrase, 0, textSize);
ctx1.restore();
var textPixels = ctx1.getImageData(0, 0, c1.width, c1.height);
var colorFg = toColor(fg);
var colorBg = toColor(bg);
var destPixels3 = ctx1.getImageData(0, 0, c1.width, c1.height);
process(textPixels, destPixels3, colorBg, colorFg);
ctx2.putImageData(destPixels3, 0, 0);
//for comparison, show Comparison Text without anti aliaising
ctx2.font = font;
ctx2.fillStyle = "rgb(255, 0, 0)";
ctx2.fillText(phrase, 0, textSize*2);
};
renderOnce();
I also added a comparison text object so that you can see the anti-aliasing working.
Hope this helps someone!
There is some subpixel antialiasing done, but it is up to the browser/OS.
There was a bit of an earlier discussion on this that may be of help to you.
I don't have an android or iOS device but just for kicks, try translating the context by (.5, 0) pixels before you draw and see if that makes a difference in how your text is rendered.