Big image drawn on small canvas become blurrier - javascript

In my project i have a canvas (200*150) and i want to draw an image of size (800*600) . The result is that image become bluer (not smooth and clear) , but if we put that image on img tag it look well. So how can we deal with this? thanks.
<img src="http://www.drivingkids.com/wp-content/uploads/2010/07/preschool-math-game-for-kids-math-racing-equatations.jpg"
width="200" heigh="150" />
<canvas id="my_canvas" width="200" height="150"></canvas>
<script type="text/javascript">
window.onload = function () {
var context = document.getElementById("my_canvas").getContext("2d");
var image = new Image();
image.src = "http://www.drivingkids.com/wp-content/uploads/2010/07/preschool-math-game-for-kids-math-racing-equatations.jpg";
image.onload = function () {
context.drawImage(image, 0, 0, context.canvas.width, context.canvas.height); //dx-30, GY-28, GW+50, GH+35
}
}
</script>

Cause
Browsers can prioritize quality or performance depending on the current scenario.
For images quality is typically prioritized over performance for most pages. But for canvas performance is not as good as with for instance direct browser rendering and CSS so the interpolation with canvas may have performance prioritized before quality. Depending on browser implementation.
Solution
Luckily there is a way to work around this by sort of splitting the burden with the resizing and interpolation by doing it in two (or more) steps, or one intermediate step if you like.
The intermediate step will first scale the image 50% to an off-screen canvas. Then use that canvas to draw to the final size. For larger images more steps will perhaps be needed.
The time spent in sum is about the same due to the sum of the operations to get the new sizes (more simply put: less to interpolate with intermediate step x2, versus more to interpolate x1) so you won't notice much performance reduction.
But most importantly: the result will be better than with just a single step.
Implementation
This is how you can implement an intermediate step:
image.onload = function () {
/// create an extra step for re-sizing image
var tmpCanvas = document.createElement('canvas'),
tmpContext = c.getContext('2d');
/// set this canvas to 50% of image
tmpCanvas.width = image.width * 0.5;
tmpCanvas.height = image.height * 0.5;
/// draw image step 1
tmpContext.drawImage(image, 0, 0, image.width * 0.5, image.height * 0.5);
/// draw image step 2
context.drawImage(tmpCanvas, 0, 0, canvas.width, canvas.height);
}
Demo (proof-of-concept)
ONLINE DEMO HERE
The result will be:
Left image: IMG element. Right image: canvas two steps (rendered in Firefox)
As you can see there is now no noticeable difference between image and canvas element.

Related

Fetch Urls and merge into single high quality image (Javascript)

I've currently got a piece of JS that allows me to fetch all the IMG urls inside a certain DIV (vpc-preview). I'm now looking for a solution to take these Urls and merge them into one image that pops up for the user to download.
I'm using this method rather than something like the "HTMLCanvasElement.toDataURL" method as I want a high quality image output. I found the Html2canvas method generates a blurry image especially on retina. The original URL's are high quality images which would be ideal if just merged.
Note: The images will not have consistent names and there can be anywhere between 5-30 images stacked on top of eachother. Which is why I'm collecting them from the container they reside in as that and the height/width are the only constants.
Below is a Fiddle I've put together to show what I have so far, the only difference is the images will be stacked on top of each other rather than side by side.
Demo Fiddle
function img_find() {
var imgs = document.querySelectorAll('#vpc-preview img');
var imgSrcs = [];
for (var i = 0; i < imgs.length; i++) {
imgSrcs.push(imgs[i].src);
}
return alert(imgSrcs);
}
UPDATE: The following fiddle lets users upload images and then merges them into a single photo for download. Unfortunately my coding skills aren't good enough to manipulate this into using the SRC urls of images within certain DIV and to Merge all the photos on top of each other. Click here to see thread this came from.
Fiddle
function addToCanvas(img) {
// resize canvas to fit the image
// height should be the max width of the images added, since we rotate -90 degree
// width is just a sum of all images' height
canvas.height = max(lastHeight, img.width);
canvas.width = lastWidth + img.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (lastImage) {
ctx.drawImage(lastImage, 0, canvas.height - lastImage.height);
}
ctx.rotate(270 * Math.PI / 180); // rotate the canvas to the specified degrees
ctx.drawImage(img, -canvas.height, lastWidth);
lastImage = new Image();
lastImage.src = canvas.toDataURL();
lastWidth += img.height;
lastHeight = canvas.height;
imagesLoaded += 1;
}

Grid with clicable irregular shapes

I'm having a bit of trouble here to develop this functionality since it must work on IE9+ so css clip-path is not an option ( http://caniuse.com/#feat=css-clip-path ).
The issue:
I need to create a grid composed of 6 elements.
Each element is an image.
The images can be different according to user answers before getting to the grid page.
Eeach element / image must be clicable and will acquire a "selected" class that will overlay div with text and background image.
image:
What is the best way to achieve this?
One way to do this could be to save out each combination of the six images you require into one big image. Then, depending on the user's answer combination, you insert the corresponding image as a background-image of a div. You then overlay click-able hotspots within the same div that roughly correlate to the dividing edges.
This may however not be the most practical solution and largely depends on how many answers/images you are dealing with.
Alternatively you could draw SVG shapes and set their fills to the images you require.
I can recommend Raphael.js as a starting point. You should be able to find what you need in the documentation
Another option would be to use HTML5 canvas:
http://jsfiddle.net/julienbidoret/GKP7X/1/
(credit goes to julienbidoret for the jsfiddle)
Javascript:
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var img = document.createElement('IMG');
img.onload = function () {
ctx.save();
ctx.beginPath();
ctx.moveTo(20, 0);
ctx.lineTo(240, 0);
ctx.lineTo(220, 240);
ctx.lineTo(0, 240);
ctx.closePath();
ctx.clip();
ctx.drawImage(img, 0, 0);
ctx.restore();
}
img.src = "http://upload.wikimedia.org/wikipedia/commons/2/2b/Clouds.JPG";
HTML:
<canvas id="c" width="300" height="300" ></canvas>
Both SVG and canvas are supported in IE9.

Rotate Moving Object on Canvas

I'm trying to draw an object based on it's rotate property, but it seems to get confused when the object is moving.
ctx.save();
ctx.translate(this.width * 0.5, this.height*0.5);
ctx.rotate(DegToRad(45));
ctx.translate(-this.width*0.5,-this.height*0.5);
ctx.drawImage(this.img, this.spriteOffset, 0, this.width, this.height, this.x, this.y,this.width,this.height);
ctx.restore();
The image is drawn rotated 45 degrees, however it now moves in a down-left direction, when the object should only be moving downwards. The movement code is simply handled by incrementing the this.y position. Is there a simpler way to accomplish this?
This is because the whole canvas is being rotated.
What you could try is looking into a framework like Kinetic JS, which creates a sort of SVG API for the canvas.
This site has tons of information on how to use it as well, including rotation, transition, transformation, and pretty much anything else that you might need in working with it.
This should suit your needs rather well.
I think I should provide a low-level, framework-free option as well. Basically, using raw javascript and HTML to pull this off.
Now, as I understood your question, you are trying to make an object (let's assume a black square) move downwards AND have it spin. The only way I can think of to spin it in the canvas without going into path hell is to rotate the entire rendering context. BUT you can also import an image into canvas, for instance, a transparent black diamond (i.e. that same square rotated). So you'd use a separate canvas to render each step of the rotation for the square.
Essentially something like this:
var canvas2 = document.createElement('canvas'), ctx2 = canvas2.getContext('2d');
//do something with the second canvas
//let's assume the second canvas is the same size as the square
ctx.drawImage(canvas2, squareX, squareY);
See my attempt
As you can see, it is a bit wonky, but it does do essentially what the question asks; it moves the square down, and rotates it. It also plots the result of that rotation below the actual canvas so you can see what's happening under the hood, but the square cuts out due to the "center" being on the top left of the square, and not in the middle.
In the end, it really comes down to how you want to do it.
I was playing around with the API and found it was easier to just keep track of where the object should be. here's an example of a square moving diagonally across the screen and rotating.
<canvas id="canvas" style="width: 100%; height: 100%;">
</canvas>
<script>
var DELAY = 15; // ms
var RECT_WIDTH = 100; // px
var RECT_HEIGHT = 100; // px
var canvas = document.getElementById('canvas');
// set intrinsic dimensions
canvas.width = 1000;
canvas.height = 1000;
var ctx = canvas.getContext('2d')
ctx.fillStyle = 'teal';
var step = 0
var vx = 2
var animate = setInterval(function () {
ctx.resetTransform()
ctx.clearRect(0, 0, 1000, 1000);
ctx.translate(vx * step, vx * step);
// rotation in place, translate to center of square and back
ctx.translate(RECT_WIDTH / 2, RECT_HEIGHT / 2);
ctx.rotate((Math.PI / 180) * step);
ctx.translate(-(RECT_WIDTH / 2), -(RECT_HEIGHT / 2));
// Draw the rectangle
ctx.fillRect(0, 0, RECT_WIDTH, RECT_HEIGHT);
step = step + 1
}, DELAY)
setTimeout(function () {
clearInterval(animate);
}, 5000);
</script>
Here, I use vx and keep track of the steps to translate, and calculate what the rotation will be in radians based on the steps a new not caring what the previous state was. Make sure you rotate across the center of where you're square will be as well.
Use ctx.translate to set the object's position before applying the rotation. This should fix the problem.
ctx.save();
ctx.translate(this.x, this.y);
ctx.translate(this.width * 0.5, this.height*0.5);
ctx.rotate(DegToRad(45));
ctx.translate(-this.width*0.5,-this.height*0.5);
ctx.drawImage(this.img, this.spriteOffset, 0, this.width, this.height, 0, 0,this.width,this.height);
ctx.restore();

Resize HTML5 canvas element

How can I achieve, so that the HTML5 canvas element ist resizeable?
I would like to implement this element so that you can scale it in any size. The event of the scaling should be the mouse which snaps the edge and the user resizes the element.
I've already read about how you can achieve this on a object in the canvas element. But in my case I need this on the canvas element itself (<canvas>).
Setting a canvas's width or height properties has the effect of clearing the canvas. Even canvas.width = canvas.width; will cause you to lose everything in the canvas. Sometimes this is desirable, but in your case it probably isn't.
What you will probably need to do is something like this
var myCanvas = document.getElementById('myCanvas');
var tempCanvas = document.createElement('canvas');
tempCanvas.width = myCanvas.width;
tempCanvas.height = myCanvas.height;
// save your canvas into temp canvas
tempCanvas.getContext('2d').drawImage(myCanvas, 0, 0);
// resize my canvas as needed, probably in response to mouse events
myCanvas.width = newWidth;
myCanvas.height = newHeight;
// draw temp canvas back into myCanvas, scaled as needed
myCanvas.getContext('2d').drawImage(tempCanvas, 0, 0, tempCanvas.width, tempCanvas.height, 0, 0, myCanvas.width, myCanvas.height);
In most browsers, the scaling will be done with a bicubic scaling algorithm, causing it to get blurry. In some cases you can set a CSS property to cause nearest neighbor on the canvas if you want, but browser support for this is very spotty right now. You can instead manually do a nearest neighbor scale , as this question shows: How to stretch images with no antialiasing
Alternative CSS Approach
Another approach is to scale the canvas using CSS. In Chrome/Safari/IE you can just do:
<canvas style="zoom:200%" />
In Firefox, you can use a scale transform to achieve the same effect:
<canvas style="-moz-transform:scale(2)" />
In many ways this approach is easier, but it comes with its own little gotchas and browser specific quirks.
I think you need to bind the onresize event to your body of document.
Then inside the the event you need to resize the canvas using window.innerWidth and window.innerHeight.
Have a look # http://kile.stravaganza.org/lab/js/canvas_resize/ (view source)
Although it's a bit late to answer this question, I'd like to share what I found to solve the same question. Take it a look please.
panel.css
#Panel {
width: 100%;
height: 30%;
}
app.js
var widthG = 0, height=G = 0;
function updateScale() {
widthG = parseInt($('#Panel').width());
heightG = parseInt($('#anel').height());
}
...
function updateCanvas() {
ctx = $('#canvas').get(0).getContext('2d');
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
ctx.canvas.width = widthG;
ctx.canvas.width = heightG;
}
I also tried to re-assign these properties by css syntax, but it doesn't work.
$('#canvas').width(panelW);
$('#canvas').height(panelH);
Hope this helps ppl suffered from the same question.

canvas - layer small image on larger background image, merge to single canvas pixel data

How do I merge a smaller image on top of a larger background image on one canvas. The smaller image will move around. So I will need to keep reference of the original background pixel data, so that the each frame the canvas can be redrawn, with the overlay in its new position.
BgImage: 1280 x 400, overlayImage: 320 x 400, overlayOffsetX: mouseX
I think it is common to draw whole scene each time you want to change something, so:
context.drawImage(bgImage, 0, 0);
context.drawImage(overlayImage, overlayOffsetX, 0);
UPDATE
You could manually compose image data of two images with making copy of background image data
or
do something easier, probably faster. You could create new canvas element (without attaching to the document) which would store image data in easy to manage form. putImageData is good if you want to place rectangular image into the canvas. But if you want to put image with transparency, additional canvas will help. See if example below is satisfying you.
// preparation of canvas containing data of overlayImage
var OVERLAY_IMAGE_WIDTH = 320;
var OVERLAY_IMAGE_Height = 400;
var overlayImageCanvas = document.createElement('canvas');
overlayImageCanvas.width = OVERLAY_IMAGE_WIDTH;
overlayImageCanvas.height = OVERLAY_IMAGE_HEIGHT;
overlayImageCanvas.getContext('2d').putImageData(overlayImage, 0, 0);
// drawing scene, execute this block every time overlayOffsetX has changed
context.putImageData(bgImage, 0, 0);
context.drawImage(overlayImageCanvas, overlayOffsetX, 0);

Categories

Resources