I have the following code:
https://jsfiddle.net/8o3sn9mh/17/
<canvas id='c' style='position:absolute; left:0px; top:0px;'>
</canvas>
<script>
var
canvas = document.getElementById('c'),
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var flower = new Image();
flower.src = "http://plainicon.com/dboard/userprod/2803_dd580/prod_thumb/plainicon.com-46129-128px-af2.png"
flower.onload = function(){
ctx.drawImage(flower,0,0,128,128, 0, 0, 30, 30);
ctx.drawImage(flower,0,0,128,128, 0, 0, 218, 218);
</script>
Short story: as you can see the flower doesn't resize well, it's quality is lost.
Long story:
I am making a game with shapes. basically, I use image with ratios such as 128-128 or 80-80 since its designed for phones, and I make sure when I resize the images to keep that 1:1 ratio. With calculations based on the user's window, I decide how much to resize the images proportionally since the canvas is on full screen. It works decent on some screen but on some it doesn't - the images can downscale too much and look poor or upscale too much and look unclear. it is most noticeable on triangle images(I know I can draw pure shapes with canvas but I need to draw faces on them so it is impossible). Any good method to do nicely?
That is the nature of pixel based images.
When downscaling try to keep the image sizes whole ratios, it will improve the quality.
Eg you have 128, then you reduce it to 30, that means pixels are reduced by 128/30 = 4.2666. Pixels are discrete units of information and can not easily be cut into smaller units without loss of quality.
If you changed the size to 32 pixels 128/32 = 4 you get better quality because you are not cutting up any pixels.
For upscaling there is little you can do, Its either blurry or pixelated. Your source images should always be at a higher resolution than you need. Rule of thumb is "only downscale".
If the quality of the images downscaled with whole ratios is not to your liking then the best option is do the scaling in production on photoshop (or the like), then let the device select which images to download. This will give you the best possible quality for all devices and save you and the users some bandwidth (for smaller res devices)
Related
I am working on a slide show editor with a slide-preview strip. I am using electron and react. Each of my slides consists of a background canvas for images/text and a foreground canvas for drawing. My drawing performance is good until I have ~20 slides in my preview strip, then drawing slows down terribly. I am drawing on mouse move event so the user sees it trailing their cursor.
I think I have narrowed down my problem to the ctx.stroke() line when I am drawing on the preview slide. The preview slide is much smaller but has the same resolution as the main canvas the user is actually drawing on.
Is there any way to make this more performant when having >40 canvases on the screen?
apply(ctx, pointIndex) {
let points = this.path.getPoints().slice(pointIndex);
ctx.lineWidth = this.getLineWidth();
if(points.length === 1){
ctx.fillRect(points[0].x - (this.getLineWidth() / 2), points[0].y - (this.getLineWidth() / 2), this.getLineWidth(), this.getLineWidth());
return;
}
ctx.beginPath();
ctx.moveTo(Math.floor(points[0].x), Math.floor(points[0].y));
points.slice(1).forEach(function(point) {
ctx.lineTo(Math.floor(point.x), Math.floor(point.y));
});
ctx.stroke();
ctx.closePath();
ctx.lineWidth = 1;
pointIndex = this.path.getPoints().length - 1;
return pointIndex;
}
To answer your question: Yes, It takes longer to redraw as you increase the total canvas size. You are dramatically increasing the total canvas size by adding full sized canvases even if those canvases are scaled down to be "slide size". BTW, .stroke is not your problem -- it renders very quickly.
Each of your slide canvases has the same resolution as your main canvas. The DOM must remember the original size so total canvas size increases dramatically with each new slide.
A fix is to make the each slide canvases smaller (== the display size), rather than keeping the same resolution as the main canvas. If the user wants to redraw a slide then dynamically create a full-sized canvas from points for that slide. Less total canvas size == better performance.
Canvas has a scaling command that lets you easily take your full-sized canvas content and scale it to smaller display size:
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
c.width=smallSlideWidth;
c.height=smallSlideHeight;
var scale=Math.min((slideWidth/mainCanvasWidth),(slideHeight/mainCanvasHeight));
cctx.scale(scale,scale);
... now draw your slide using the same coordinates as your main canvas
We don't have more of your code, but if you're redrawing every slide canvas all the time -- don't do that!
I am making a whiteboard with angularjs, socketio an node.js.
As long as I use a fixed width/height for the canvas everywhere I can just broadcast the coordinates of the mouse/touch event and recreate the graphic in realtime. However, the problem I am facing is when trying to make the canvas have different sizes across different platforms (think desktop and a smartphone), the canvas has to be scaled and so does the graphic, but this makes things pretty slow.
The approach I am currently taking is to draw the graphic in a temporary hidden canvas of original size, then when there is a pause in the drawing stream (in other words the user has stopped doodling), I scale and copy it to the main canvas. The problem with this is, it doesn't feel very realtime at all, especially when a user keeps doodling without a pause for a while. Another approach I could try is to push all the coordinates in an array, apply 2d affine transformation on it, then redraw the entire thing. Though this too doesn't seem like a good solution for when the array size increases, repeatedly trying to apply transformations in realtime can easily eat up a lot of resources.
Is there any better way to achieve this?
do it with css scale transform. Scale the canvas to fit the size of the device be it a mobile, desktop, or tablet, or whatever.
Have your canvas be fixed width across all devices. Say it's 640px by 480px. Now we'll resize it to fit whatever window.
$(window).resize(function() {
var w = 640; // $('#mycanvas').width();
var h = 480; // $('#mycanvas').height();
var newWidth = $(window).width(); // this could be either smaller or bigger than the canvas
var newHeight = $(window).height(); // same here
var scaleX = newWidth / w;
var scaleY = newHeight / h;
$('#mycanvas').css('transform','scale(' + scaleX + ',' + scaleY +')');
$('#mycanvas').css('-webkit-transform','scale(' + scaleX + ',' + scaleY +')');
});
note: mycanvas could also be a container if any kind that holds the canvas and other divs or whatever. just make sure that w is the width of that container, etc.
note: if someone uses their fingers to draw across the screen, you may need to convert it from scaled coordinates to fixed width (640x480) coordinates.
Btw, I found a different way to do this that might be better. not sure:
Scaling canvas element with static resolution
When I first started this project that I'm working on, my canvas size with 1400px wide and 480px tall. I realized that I am going to need to make the canvas the same size as the window itself later, so I did that and everything inside of the canvas zoomed in or something. I set a drawImage(); to be 300 px wide and 180 px tall, and it is a LOT bigger than that, the image is actually the same width as the canvas now. Any suggestions? Here's the link to the project:
http://brycemckenney.com/animation-app
Thank you guys!
You have set the dimensions through css, instead of the physical dimensions of the (image) canvas.
The relevant piece (for others to read in the future) of your code is:
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");
var windowHeight = $(window).height();
var windowWidth = $(window).width();
$(canvas).css({
height: windowHeight - 8,
width: windowWidth - 8
});
Think of it like this: suppose you have a normal jpg-image.
That jpg has it's own 'physical' dimensions (aka width and height).
In both HTML and CSS you can set the dimensions (in px, percent, etc) that you'd like the browser to render (scale) the picture (hey, the picture already has a immutable size right?).
Now for canvas:
In order for canvas to have a physical width/height, you have to set the .width and .height of the canvas-element itself, either in HTML or per javascript (a side-effect is that setting the physical dimensions is that the canvas will clear itself, as per spec).
Then to scale the image (like you did with the above jpg example) you use css (again in px/percent/etc).
I think this is a clever solution by the way to add that new canvas-element to the HTML-Spec!
So, rounding up:
A canvas with a width and a height of 300 px rendered as 100% of a container (like document.body) that measures 900x900px will be scaled-up 3 times!
The reverse (scaling down) will let you draw even more crisp lines by the way!
Hope this helps your understanding!
I just started using EaselJS for a project I'm working on and I'm a bit stuck with the Bitmap Class.. What I do is add a 3000 x 4000 image to the canvas / stage and let the user scale and rotate it. Mainly I'm using the following function:
#width = 3000
#height = 4000
#scale = 0.2
#bitmap.setTransform( 0, 0, #scale, #scale, 200, 0, 0, #width*#scale/2, #height*#scale/2 )
This all works except for the registration point. The number given to the function is half the image width / height, so should be good. But the rotation is still not from the center..
Also I'm looking for a way to increase the quality of this Bitmap or the Stage.. When the Bitmap is scaled to 0.2, the image isn't visible at all, just a bunch of big blocks / pixels..
Hope someone can help me out here,
Thanks in advance
Stupid mistake, my canvas was scaled up so the quality was low even if the image wasn't scaled.
The width and height attributes define the canvas resolution.
The css style width and height define the size of the canvas.
For everybody having problems with quality, you can oversample the canvas:
<style>
canvas {
width:200px;
height:100px;
}
</style>
<canvas width='400' height='200' />
Source:
http://blog.headspin.com/?p=464
Right now I have one large canvas where everything is drawn. The canvas is created directly in the JS file:
var canvas = document.createElement ("canvas");
var ctx = canvas.getContext("2d");
canvas.width = document.width;
canvas.height = document.height;
document.body.appendChild(canvas);
It resizes itself to the browser window.
I have a gradient background being drawn onto the one canvas along with all the other elements. The colors for the background are randomly generated at each game mode change (eg when the main menu is toggled to the game, then the level end screen, etc). Currently I'm drawing these onto the canvas like this:
var grad1 = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, 500);
grad1.addColorStop(0, bgGradStop1);
grad1.addColorStop(1, bgGradStop2);
ctx.fillStyle = grad1;
ctx.fillRect(0, 0, canvas.width, canvas.height);
I read numerous times that having a separate canvas for the background is better for performance. Is there anything specific that I need to do to make this effective or can I simply create another canvas and draw the background using the same exact code, only modified to use the other canvas, like this:
var grad1 = ctxBg.createRadialGradient(canvasBg.width / 2, canvasBg.height / 2, 0, canvasBg.width / 2, canvasBg.height / 2, 500);
grad1.addColorStop(0, bgGradStop1);
grad1.addColorStop(1, bgGradStop2);
ctxBg.fillStyle = grad1;
ctxBg.fillRect(0, 0, canvasBg.width, canvasBg.height);
Or does getting any performance benefit involve some totally different method of using the second canvas?
Also, if it really is just a matter of doing the exact same thing but on a different canvas, would there be any benefit to also for example moving the HUD text to its own canvas while other entities are moving? Even separating various types of entities onto their own canvases? How far does the benefit of multiple canvases actually stretch?
Or does getting any performance benefit involve some totally different method of using the second canvas?
You've got the right idea of how to do it, though I don't think a second canvas is necessary in your case.
The important thing from a performance perspective is that you don't want to have to keep constructing and filling the gradient. If all you're doing in your draw loop is:
ctx.fillStyle = grad1;
ctx.fillRect(0, 0, canvas.width, canvas.height);
Then you should be pretty swell here. I don't think having a second canvas in the background will help you much after that. There might be a slight performance boost, but who wants to have to keep track of additional DOM elements if you don't have to?
It's a little hard to test the performance of having a second canvas behind your main one, but instead of having two large canvases in the DOM one similar alternative is to draw the gradient an in-memory canvas and always call ctx.drawImage(inMemCan, 0, 0); instead of setting the gradient and drawing it. Drawing images is known to be fast, and using two canvases this way is probably close to the speed you could expect from two on-page canvases (and hopefully it would be faster).
So we could test drawing the gradient from an in-memory canvas to your main canvas versus drawing the plain old gradient. Here's a simple test with a large canvas:
http://jsperf.com/gradient-vs-immemcan
They're pretty equal it seems. If this one thing is the only thing in your background I wouldn't worry about it. There are probably bigger performance fish for you to fry first.
I'd save the benefit of multiple canvases for when a relatively complicated background updates rarely but independently of the foreground. If your background was instead made with 30 gradients and some paths, then using a second canvas (or an in-memory canvas to cache an image) would give you a sizable boost.
It resizes itself to the browser window.
Just a reminder that the full screen API works pretty well in webkit and firefox if you want to look into it in the future.