At the end of this question is an SSCCE to display a 24000x12000 Miller projection of satellite imagery on an HTML5 canvas. It has a couple problems:
Rather than showing the entire image in one screen as I desire only a small section of the upper left corner is displayed.
The image suffers from extreme pixellation that should not appear at the scale displayed because the image is very high resolution
The image used is available at Wikimedia. I renamed it "world.jpg".
This is not a normal web application and the large image will not be downloaded during application execution so any advice to not require downloading such a large image is moot.
The application will dynamically zoom into various map areas, thus the large image size.
In the real code I use an external stylesheet and javascript file. I integrated them into the html solely for the SSCCE.
This is the code.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>The Earth</title>
<script type="text/javascript">
function draw() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var map = new Image();
map.src = "world.jpg";
map.onload = function() {
var width = window.innerWidth;
var height = window.innerHeight;
ctx.drawImage(map, 0, 0, width, height);
};
}
</script>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0px;
}
#canvas {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
background-color: rgba(0, 0, 0, 0);
}
</style>
</head>
<body onload="draw();">
<canvas id="canvas"></canvas>
</body>
</html>
Use the version of context.drawImage that clips and scales the original image:
context.drawImage(
sourceImage,
clipSourceAtX, clipSourceAtY, sourceClipWidth, sourceClipHeight,
canvasX, canvasY, canvasDrawWidth, canvasDrawHeight
)
For example, assume that you are focusing on coordinate [x==1000,y==500] on the image.
To display the 640px x 512px portion of the image at [1000,500] you can use drawImage like this:
context.drawImage(
// use "sourceImage"
sourceImage
// clip a 640x512 portion of the source image at left-top = [1000,500]
sourceImage,1000,500,640,512,
// draw the 640x512 clipped subimage at 0,0 on the canvas
0,0,640,512
);
A demo: http://jsfiddle.net/m1erickson/MtAEY/
Here's the working javascript code, formatted nicely.
function draw() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var map = new Image();
map.src = "world.jpg";
map.onload = function() {
var width = window.innerWidth;
var height = window.innerHeight;
canvas.width=width;
canvas.height=height;
var mapWidth=map.width;
var mapHeight=map.height;
var scale=scalePreserveAspectRatio(mapWidth,mapHeight,width,height);
ctx.mozImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.drawImage(map, 0, 0, mapWidth, mapHeight, 0, 0, mapWidth*scale, mapHeight*scale);
};
}
function scalePreserveAspectRatio(imgW,imgH,maxW,maxH){
return(Math.min((maxW/imgW),(maxH/imgH)));
}
The key is the two lines
canvas.width=width;
canvas.height=height;
Simply setting these to 100% in CSS doesn't work, the 100% is apparently not 100% of the inner dimensions of the window as I assumed. Since these dimensions are dynamic they must be set via JS not CSS.
Related
I want to make a drawing app and I can't get the basic functionality to pan a layer nor to edit the svgs and scale, transform.
With paper.js the canvas can be easily resized and fit a div but I need the fabric.js to move and scale objects. Fabric does not resize or fit a div.
In html I have this : <canvas id="canvas" resize></canvas>
and css :
#canvas {
width: 100%;
height: 100%;
background-color: transparent;
}
With this code I can resize the window and the canvas.
But when I use fabric.js it goes back to 300 x 150 and doesn't resize:
var fabric = require('fabric').fabric;
canvas = new fabric.Canvas('canvas');
paper.install(window);
window.onload = function() {
// Setup directly from canvas id:
paper.setup('canvas');
var path = new Path();
path.strokeColor = 'black';
var start = new Point(100, 100);
path.moveTo(start);
path.lineTo(start.add([ 200, -50 ]));
view.draw();
}
I don't think that using fabric.js with paper.js is a good idea as they might have some conflicting points.
If what you lack from fabric.js is the ability to resize the canvas, here is a fiddle demonstrating how you can do.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Debug</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.1.0/fabric.js"></script>
<style>
body {
margin : 0;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// Init Fabric.js canvas
var canvas = new fabric.Canvas('canvas');
// Draw rectangle
canvas.add(new fabric.Rect({
left: 200,
top: 200,
width: 500,
height: 500,
fill: '#D81B60',
hasControls: true
}));
// Bind and call resize callback
window.onresize = resize;
resize();
// On resize...
function resize() {
// ...set canvas size to window size
canvas.setWidth(window.innerWidth);
canvas.setHeight(window.innerHeight);
}
</script>
</body>
</html>
I have the following in html:
<img id="MarkingImage"></img>
<canvas id="MarkingCanvas"></canvas>
And on css:
#MarkingCanvas { width: 100%; background-color: #7986CB; height: 96%; }
#MarkingImage { position: absolute; }
And on js:
function LoadImage()
{
var markingImage = new Image();
markingImage.addEventListener('load', function()
{
m_imageWidth = this.naturalWidth;
m_imageHeight = this.naturalHeight;
m_markingCanvasContext.drawImage(markingImage, 0, 0, m_imageWidth, m_imageHeight);
});
markingImage.src = 'PICT0001.JPG';
}
The thing is that this output stretches the image and it's quality gets really poor (see attached "Original" and "Result").
When I debug, I can see that the sizes if my canvas are:
width - 1410
height - 775
The sizes if my image are:
width - 551
height - 335
So my question is: why isn't the image placed in it's original size? I mean, there's enough space on the canvas. Also, why does the image gets stretched, and as a result gets in pretty low quality. Appears like it stretches beyond the size of the canvas.
What am I missing here?
You need to set the canvas width and height dynamically according to the browser window's width and height. also, there is no need to use the image's naturalWidth / naturalHeight.
var m_markingCanvas = document.getElementById('MarkingCanvas');
var m_markingCanvasContext = m_markingCanvas.getContext('2d');
// setting canvas width and height dynamically
m_markingCanvas.width = window.innerWidth;
m_markingCanvas.height = window.innerHeight;
function LoadImage() {
var markingImage = new Image();
markingImage.addEventListener('load', function() {
m_markingCanvasContext.drawImage(this, 0, 0);
});
markingImage.src = 'https://i.stack.imgur.com/a3Wpy.jpg';
}
LoadImage();
<canvas id="MarkingCanvas"></canvas>
I am trying to draw the following image to a canvas but it appears blurry despite defining the size of the canvas. As you can see below, the image is crisp and clear whereas on the canvas, it is blurry and pixelated.
and here is how it looks (the left one being the original and the right one being the drawn-on canvas and blurry.)
What am I doing wrong?
console.log('Hello world')
var c = document.getElementById('canvas')
var ctx = c.getContext('2d')
var playerImg = new Image()
// http://i.imgur.com/ruZv0dl.png sees a CLEAR, CRISP image
playerImg.src = 'http://i.imgur.com/ruZv0dl.png'
playerImg.width = 32
playerImg.height = 32
playerImg.onload = function() {
ctx.drawImage(playerImg, 0, 0, 32, 32);
};
#canvas {
background: #ABABAB;
position: relative;
height: 352px;
width: 512px;
z-index: 1;
}
<canvas id="canvas" height="352" width="521"></canvas>
The reason this is happening is because of Anti Aliasing.
Simply set the imageSmoothingEnabled to false like so
context.imageSmoothingEnabled = false;
Here is a jsFiddle verson
jsFiddle : https://jsfiddle.net/mt8sk9cb/
var c = document.getElementById('canvas')
var ctx = c.getContext('2d')
var playerImg = new Image()
// http://i.imgur.com/ruZv0dl.png sees a CLEAR, CRISP image
playerImg.src = 'http://i.imgur.com/ruZv0dl.png'
playerImg.onload = function() {
ctx.imageSmoothingEnabled = false;
ctx.drawImage(playerImg, 0, 0, 256, 256);
};
Your problem is that your css constraints of canvas{width:512}vs the canvas property width=521will make your browser resample the whole canvas.
To avoid it, remove those css declarations.
var c = document.getElementById('canvas')
var ctx = c.getContext('2d')
var playerImg = new Image()
// http://i.imgur.com/ruZv0dl.png sees a CLEAR, CRISP image
playerImg.src = 'http://i.imgur.com/ruZv0dl.png'
playerImg.width = 32
playerImg.height = 32
playerImg.onload = function() {
ctx.drawImage(playerImg, 0, 0, 32, 32);
};
#canvas {
background: #ABABAB;
position: relative;
z-index: 1;
}
<canvas id="canvas" height="352" width="521"></canvas>
Also, if you were resampling the image (from 32x32 to some other size), #canvas' solution would have been the way to go.
As I encountered this older post for some of my issues, here's even more additional insight to blurry images to layer atop the 'imageSmoothingEnabled' solution.
This is more specifically for the use case of monitor specific rendering and only some people will have encountered this issue if they have been trying to render retina quality graphics into their canvas with disappointing results.
Essentially, high density monitors means your canvas needs to accommodate that extra density of pixels. If you do nothing, your canvas will only render enough pixel information into its context to account for a pixel ratio of 1.
So for many modern monitors who have ratios > 1, you should change your canvas context to account for that extra information but keep your canvas the normal width and height.
To do this you simply set the rendering context width and height to: target width and height * window.devicePixelRatio.
canvas.width = target width * window.devicePixelRatio;
canvas.height = target height * window.devicePixelRatio;
Then you set the style of the canvas to size the canvas in normal dimensions:
canvas.style.width = `${target width}px`;
canvas.style.height = `${target height}px`;
Last you render the image at the maximum context size the image allows. In some cases (such as images rendering svg), you can still get a better image quality by rendering the image at pixelRatio sized dimensions:
ctx.drawImage(
img, 0, 0,
img.width * window.devicePixelRatio,
img.height * window.devicePixelRatio
);
So to show off this phenomenon I made a fiddle. You will NOT see a difference in canvas quality if you are on a pixelRatio monitor close to 1.
https://jsfiddle.net/ufjm50p9/2/
In addition to #canvas answer.
context.imageSmoothingEnabled = false;
Works perfect. But in my case changing size of canvas resetting this property back to true.
window.addEventListener('resize', function(e){
context.imageSmoothingEnabled = false;
}, false)
The following code works for me:
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
};
img.src = e.target.result; // your src
Simple tip: draw with .5 in x and y position. like drawImage(, 0.5, 0.5) :D There you get crisp edges :D
There is a shadow on the bottom and right sides of a rectangle I've drawn in the canvas on the upper-left hand corner of my window here:
http://thomasshouler.com/datavis/gugg/ratio.html
I haven't set any positive values to the relevant context attributes (shadowBlur, shadowOffsetY, etc.), so what gives?
Canvas code snippet:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.shadowBlur=0;
ctx.shadowOffsetY=0;
var width = 50;
var height = 4;
var grd=ctx.createLinearGradient(0,0,width,0);
grd.addColorStop(0,'#008000');
grd.addColorStop(0.5,'#CCCCCC');
grd.addColorStop(1,'#FF0000');
ctx.fillStyle=grd;
ctx.fillRect(0,0,width,height);
Any wisdom would be greatly appreciated.
It's related to scaling as the canvas is displayed. Your original fragment with a very small gradient and scaled up http://jsfiddle.net/CBzu4/ clearly shows the blurred outline.
Drawing larger (making sure there is no css scaling) it looks fine: http://jsfiddle.net/CBzu4/1/ and the code is the same as yours, just rearranged:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var width = 50 * 8;
var height = 4 * 8;
var grd=ctx.createLinearGradient(0,0,width,0);
grd.addColorStop(0,'#008000');
grd.addColorStop(0.5,'#FFFF00');
grd.addColorStop(1,'#FF0000');
ctx.fillStyle = grd;
ctx.fillRect(5,5,width,height);
Same code, but this time the canvas scaled up with the embedded style: http://jsfiddle.net/CBzu4/2/
<canvas id="myCanvas" style="border: 1px solid red; width: 120%; height: 120%;" width="600" height="80">
The final version is again the same as yours, no css scaling at all and no blurred outline: http://jsfiddle.net/CBzu4/3/
I have slices of images, like 0_0.jpg, 0_1.jpg, 1_0.jpg. 1_1.jpg
I want to combine them to form one single image.
I have a canvas element already.
what library, or algorithm can do this?
UPDATE :
Complete senario :
I have to load few images into a CANVAS element. SOme of them are full images, not sliced. But few are sliced into pieces. what I want is to get combine those slices image into one. Some people saying use table or css or whatever. that just won't work here. I need some technique to convert them on fly.
In order to do this with canvas, you'll have to know all the dimensions and draw the Image objects. Use the JS image object to preload your images. From there you have all the information you need to 'stitch' them together, width, height, etc...
http://www.techrepublic.com/article/preloading-and-the-javascript-image-object/5214317
// Three arguments: the element, destination (x,y) coordinates.
context.drawImage(img_elem, dx, dy);
// Five arguments: the element, destination (x,y) coordinates, and destination
// width and height (if you want to resize the source image).
context.drawImage(img_elem, dx, dy, dw, dh);
// Nine arguments: the element, source (x,y) coordinates, source width and
// height (for cropping), destination (x,y) coordinates, and destination width
// and height (resize).
context.drawImage(img_elem, sx, sy, sw, sh, dx, dy, dw, dh);
Here's a example that's close to what you want:
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
#myCanvas {
border: 1px solid #9C9898;
}
</style>
<script>
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
window.onload = function(images) {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var sources = {
darthVader: "http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg",
yoda: "http://www.html5canvastutorials.com/demos/assets/yoda.jpg"
};
loadImages(sources, function(images) {
context.drawImage(images.darthVader, 100, 30, 200, 137);
context.drawImage(images.yoda, 350, 55, 93, 104);
});
};
</script>
</head>
<body>
<canvas id="myCanvas" width="578" height="200"></canvas>
</body>
</html>
Personally, I'd simply use a table with zero padding and zero margin. Much easier IMO.
I don't know about <canvas>, but I'd just create a bunch of <img>s and position them absolutely so that they fit together. That will be simple, and work across all browsers, unlike <canvas>.